> That means that when one side of the API changes, the other side won't even compile until it's updated to match.
Coupling your front end and back end in this way can give you false confidence in making API changes. If you control both sides of the API on a web app that doesn't have too many users yet, then this can be very productive, but consider this situation:
You've made a breaking API change on the back end, and thanks to your type system, you make the corresponding front end changes as well, which is great. You deploy your code, and the back end changes take effect immediately. Visitors who were in the middle of a session are still running the old front end code, and their requests start failing. Cached copies of the front end start failing too.
You can engineer around this but it's better to have a system that doesn't make introducing breaking API changes too easy. It should be painful.
When you control both front-end and back-end, you don't have to make a breaking change that will affect anyone at all, if you're willing to do a multiple deploys and maybe wait a little bit.
If your want to change from A to B, your first deploy is deploying B without stopping A from working. It could be a totally new endpoint, or you could just throw a version field into the request body or querystring. Once everyone has switched off of A, you can remove it from your codebase so only B remains without actually affecting anyone's session.
If you don't want to pollute all your querystrings or request bodies with version fields, you could have an optional newVersion boolean field that only needs to appear during a switchover or make a temporary endpoint for updated clients to hit instead of the old endpoint:
# state at start
/foo <- uses API A
# deploy new endpoint
/foo <- uses API A
/foo/new <- uses API B
# wait for everyone to swtich to /foo/new, then deploy API to /foo, removing support for API A (this could be split into two deploys if you wanted)
/foo <- uses API B
/foo/new <- uses API B
# wait for everyone to switch over to /foo, then remove /foo/new
/foo <- uses API B
MMO games like Guild Wars handle this problem by deploying a new server version in parallel to the running old servers and keep sessions in progress connected to the old servers. New sessions will connect to the newly deployed servers, while old sessions eventually "dry out". Old server processes with no active connections shut themselves down.
IMHO that's an easier solution than having to implement a backward compatible API or other API versioning solutions.
And if you do a gradual rollout, newer clients can connect to older backends! And if clients can talk to each other, there can be THREE versions involved. Also: rollbacks.
I’m curious if anyone has a system for managing this that they love. I’ve pretty much only seen painful ways.
At <dayjob> we use flow types and graphql, so like OP our frontend will fail to compile if we make a breaking change. To assist with the backward-compatible-during-deploy issue, we additionally have a teensy bit of tooling that comments on our PRs to indicate dangerous API changes.
It's not perfect (I've ignored the comments before, thinking I knew better... I didn't), but it seems to help.
It wouldn't be difficult to make it more sophisticated, and then completely block PRs it knew weren't backward compatible, but we haven't seen a strong need to do that yet.
I think in an ideal world you would do API changes to a live system in much the same way as you do with a live Database Schema: Carefully, and in a backward compatible manner.
In https://game.bitplanets.com I have `gameVersion` that will change when I make breaking changes. Then the client kind of understands that the client version doesn't match the server version and it will refresh.
I second the sentiment though. I wouldn't choose to use plain JavaScript over TypeScript for any significant project.
You might ask why I wouldn't just use a more fully typed language like Java. My main response is that I love having the flexibility to choose how strict the types should be. Sometimes, typing something out fully is not worth the trouble. Having escape hatches lets me make that choice. While I enjoy using types to turn runtime bugs into compile time errors as much as possible, it's not the right thing to do 100% of the time.
> it's not the right thing to do 100% of the time.
What is an example of this in your experience? I am trying to think of one, but after 25 years of programming, I cannot say I have ever encountered a case where it was worth using ‘an escape hatch’ that did not bite us later on. Maybe it is the type of software/clients I work with, but ‘the trouble’ is not really something I have seem before I think.
Foregoing types isn't like other escape hatches. Sometimes you'll get odd, hard-to-understand errors from the typescript compiler. On my team, we just use Babel, so we can write TypeScript (and get the benefits of types) without have 100% statically-correct code. Getting from 99% to 100% statically correct is time spent detracting from other more valuable contributions to the business, that don't have a meaningful impact on code quality. Diminishing returns.
Both C# and Kotlin have a 'Dynamic' type for just that escape hatch. IMHO, That's a better way over TS - since types might not be the right thing 100% of the time, but they are 95% of the time.
I programmed professionally in C# for about eight years and in that time I can count on one hand the number of times I saw the ‘dynamic’ keyword. Nobody seems to think it’s worth the effort or confusion.
I saw other commenter say that the benefit of using TS in the frontend and backend, then DB updates in the backend also update the frontend code: "The beauty of Gary’s setup is changes to his database update the types for the server side Code AND client side code. The value of that comes from using the same language in both platforms"
Can you get this sort of benefit with Kotlin? How?
Not a pro C# dev but I use it quite a bit - IIRC the correct keyword for that situation would be 'var' which is similar to 'auto' in C++. It just statically infers the type from the assignment, while 'dynamic' actually does runtime casting.
I use 'var' all the time, I use 'dynamic' only when working with JSON objects and such.
That is a cool project but you’d still have to keep it in sync with your frontend code. The beauty of Gary’s setup is changes to his database update the types for the server side Code AND client side code. The value of that comes from using the same language in both platforms
You aren't bound to use the same language on both platforms to get this functionality. I had this running 3-4 years ago using C#, EntityFramework, and a d.ts generator that I forgot the name of.
A change to the DB schema just required me to refresh our edmx from the DB schema and then build.
That's not true. There are plenty of cases where certain pieces of software is cordoned off and/or non-critical that would not affect the rest of your stack if they fail.
It makes it really easy to add type safety to parts of a legacy project as you go along; for new projects you can enforce type safety with a linter or stricter compiler settings.
A more accurate remix of this analogy (for the TypeScript case) is that it is like building a wall around the part of the pool that you actually swim in, and allowing that folks can pee all they want as long as it is outside of the boundary you built.
If you control both ends of an API, there's no reason not to use Thrift or equivalent (gRPC) rather than untyped JS. Even if you don't believe in typing in general, an API boundary is exactly where it's most important to make sure both sides agree on what the interface is.
Do I read the chart correctly and their whole Codebase (minus dependencies) is less the 15k lines of code? Porting 6.5k likes of ruby in 2 weeks sounds reasonable, but how doing migrations 10x or 100x that size is far more challenging.
TypeScript has been on HN for different reasons over the last few days. The main thing that has stood out, from the flame wars erupting over it is that it
is a truly divisive idea. To some, it gives the same securities and checks provided when working with a statically typed language. To others, it curtails the power and expressiveness inherent in JS, the dynamic, functional language it compiles down to.
TypeScript does provide a lot of benefits, but for it to truly succeed and take over in the JS world would mean it has to truly also reflect, and enable JS's functional and dynamic roots. JS got where it is by being JS.. an extremely flexible and accommodating language. HTML got this far today by doing the same, just google XHTML if you doubt that. So for TypeScript to succeed where CoffeeScript failed, this may be the direction it needs to lean more towards: being less divisive, and inviting all kinds of programming paradigms to the party.
Is that really all it is though?
Cases in point: Java Applets, VB, Flash, Dart ...
All of these were at one point or another "built into the browser", but where are they now? Give credit where it is due, the success of the web as a platform lies not in its technical superiority over alternatives, but in its inclusiveness and flexibility. Any tech that is trying to replace HTML, CSS and JS in this regards will seriously have to consider and accommodate this, or suffer the same fate of hundreds of other pretenders to the throne. Long live the king! Long live open, approachable and flexible tech!
Exhibit A: List of very different and diverse languages that compile to JS ( https://github.com/jashkenas/coffeescript/wiki/List-of-langu... ). If this does not demonstrate the flexibility and malleability of the language, then I do not know what does. Take web assembly for example, which has been around for over five years now, and was specifically designed as a "compile to" language. How many languages compile to web assembly in comparison?
Any tech that is as divisive as TS is simply not going to get far. Flash ActionScript was massive 10 years ago compared to any alternative to JS tech today, and where is it now? The creator of TS even quotes ActionScript as one of the main inspirations for TS. ActionScript even had a more powerful version of Reacts JSX (ES4), where is it today? CoffeeScript was all the rage 5 years ago, JS simply absorbed all its good ideas, where is it today? The things that last, that stand the test of time are the things that are flexible and accommodate different ways of doing things. For your beloved TS to stand the test of time, it has got to accommodate the whole JS eco-system, not just those who favor the static object oriented way of doing things.
It is actually quite frustrating compared to some other languages, and more often than not you see that its just `JSON.parse` and hope for the best. I wish we had something like Rust's Serde or Haskell's aeson.
If you want to validate data coming over IO then your options are data validation libraries such joi, io-ts, and yup. You have to write seperate data validators on top of your types. io-ts has a way of deriving types from validators, but io-ts is often seen as quite intimidating being built on top of fp-ts.
No matter how carefully you maintain strict typing within your typscript project the moment you hit IO everything is basically `any`.
Some projects like openapi-generator might generate some validators for server respones, but I've not seen any good generators that do actual validation.
I'm not sure if apollo-graphql does responses validation? Does anyone know?
Apollo does indeed validate responses as well as requests. If your server tries to send an object that doesn’t match the schema, you’re going to return just a 500 error, which means your clients can trust the graphql types implicitly.
Since in my current job I can’t use graphql but have to type everything in openapi, Had to resort to writing my own lib to get to the same functionality for a REST backend in TS - https://github.com/ovotech/laminar
My personal take is that io-ts _is_ typescript’s aeson, but if you’re scared of the baggage it comes with then how about giving zod a try? https://github.com/vriad/zod
For my current project, it is handled by GraphQL. Using a combination of type-graphql and code generation, I have fully round trip static types on both client and server, by only defining my model once. Even my graphql queries are checked.
It is a bit of a pain to setup, but once it is done, I've found that it works pretty well.
I use a combination of class-transformer[1] and then class-validator[2].
First transform the JSON into proper class-objects and then validate the object for correctnes.
IMHO this has nothing to do with TypeScript because in your runtime all the type information is gone and you need to validate the remote data never the less. TypeScript can help you structure your validations but you need to write them.
I use tcomb-validation[1] to validate incoming data. The downside is that it cannot use TypeScript types so you have to define the schema twice.
For me, the duplicated types turned out to be less of a hassle than initially anticipated. In many cases, I ended up with different data structures anyway because the data is transformed in the client. I think of them as transport types and client types.
Coupling your front end and back end in this way can give you false confidence in making API changes. If you control both sides of the API on a web app that doesn't have too many users yet, then this can be very productive, but consider this situation:
You've made a breaking API change on the back end, and thanks to your type system, you make the corresponding front end changes as well, which is great. You deploy your code, and the back end changes take effect immediately. Visitors who were in the middle of a session are still running the old front end code, and their requests start failing. Cached copies of the front end start failing too.
You can engineer around this but it's better to have a system that doesn't make introducing breaking API changes too easy. It should be painful.
If your want to change from A to B, your first deploy is deploying B without stopping A from working. It could be a totally new endpoint, or you could just throw a version field into the request body or querystring. Once everyone has switched off of A, you can remove it from your codebase so only B remains without actually affecting anyone's session.
If you don't want to pollute all your querystrings or request bodies with version fields, you could have an optional newVersion boolean field that only needs to appear during a switchover or make a temporary endpoint for updated clients to hit instead of the old endpoint:
IMHO that's an easier solution than having to implement a backward compatible API or other API versioning solutions.
I’m curious if anyone has a system for managing this that they love. I’ve pretty much only seen painful ways.
It's not perfect (I've ignored the comments before, thinking I knew better... I didn't), but it seems to help.
It wouldn't be difficult to make it more sophisticated, and then completely block PRs it knew weren't backward compatible, but we haven't seen a strong need to do that yet.
Right, just version the API, or have the app refresh itself when appropriate. Isn't this a fairly straightforward issue to address?
Also, as BiteCode_dev points out, this issue has nothing to do with whether you use a static type system.
I second the sentiment though. I wouldn't choose to use plain JavaScript over TypeScript for any significant project.
You might ask why I wouldn't just use a more fully typed language like Java. My main response is that I love having the flexibility to choose how strict the types should be. Sometimes, typing something out fully is not worth the trouble. Having escape hatches lets me make that choice. While I enjoy using types to turn runtime bugs into compile time errors as much as possible, it's not the right thing to do 100% of the time.
What is an example of this in your experience? I am trying to think of one, but after 25 years of programming, I cannot say I have ever encountered a case where it was worth using ‘an escape hatch’ that did not bite us later on. Maybe it is the type of software/clients I work with, but ‘the trouble’ is not really something I have seem before I think.
I saw other commenter say that the benefit of using TS in the frontend and backend, then DB updates in the backend also update the frontend code: "The beauty of Gary’s setup is changes to his database update the types for the server side Code AND client side code. The value of that comes from using the same language in both platforms"
Can you get this sort of benefit with Kotlin? How?
A change to the DB schema just required me to refresh our edmx from the DB schema and then build.
But type safety is a spectrum anyway. One could say that not having dependent types is like having a swimming pool without a lifeguard.
Should every swimming pool in the world have a lifeguard? Probably not.
It advertises lots of benefits but the enforced schema types are worth the price of admission even if you're doing simple CRUD with no graphs.
TypeScript does provide a lot of benefits, but for it to truly succeed and take over in the JS world would mean it has to truly also reflect, and enable JS's functional and dynamic roots. JS got where it is by being JS.. an extremely flexible and accommodating language. HTML got this far today by doing the same, just google XHTML if you doubt that. So for TypeScript to succeed where CoffeeScript failed, this may be the direction it needs to lean more towards: being less divisive, and inviting all kinds of programming paradigms to the party.
That, after-all is how JS succeeded.
JS got where it is solely by being the language built into browsers.
All of these were at one point or another "built into the browser", but where are they now? Give credit where it is due, the success of the web as a platform lies not in its technical superiority over alternatives, but in its inclusiveness and flexibility. Any tech that is trying to replace HTML, CSS and JS in this regards will seriously have to consider and accommodate this, or suffer the same fate of hundreds of other pretenders to the throne. Long live the king! Long live open, approachable and flexible tech!
Exhibit A: List of very different and diverse languages that compile to JS ( https://github.com/jashkenas/coffeescript/wiki/List-of-langu... ). If this does not demonstrate the flexibility and malleability of the language, then I do not know what does. Take web assembly for example, which has been around for over five years now, and was specifically designed as a "compile to" language. How many languages compile to web assembly in comparison?
Any tech that is as divisive as TS is simply not going to get far. Flash ActionScript was massive 10 years ago compared to any alternative to JS tech today, and where is it now? The creator of TS even quotes ActionScript as one of the main inspirations for TS. ActionScript even had a more powerful version of Reacts JSX (ES4), where is it today? CoffeeScript was all the rage 5 years ago, JS simply absorbed all its good ideas, where is it today? The things that last, that stand the test of time are the things that are flexible and accommodate different ways of doing things. For your beloved TS to stand the test of time, it has got to accommodate the whole JS eco-system, not just those who favor the static object oriented way of doing things.
If you want to validate data coming over IO then your options are data validation libraries such joi, io-ts, and yup. You have to write seperate data validators on top of your types. io-ts has a way of deriving types from validators, but io-ts is often seen as quite intimidating being built on top of fp-ts.
No matter how carefully you maintain strict typing within your typscript project the moment you hit IO everything is basically `any`.
Some projects like openapi-generator might generate some validators for server respones, but I've not seen any good generators that do actual validation.
I'm not sure if apollo-graphql does responses validation? Does anyone know?
io-ts is pretty scary, though. I like the `runtypes` library but it doesn't help me express my types in a way clients can consume, which, enh.
Since in my current job I can’t use graphql but have to type everything in openapi, Had to resort to writing my own lib to get to the same functionality for a REST backend in TS - https://github.com/ovotech/laminar
https://github.com/moltar/typescript-runtime-type-benchmarks
It is a bit of a pain to setup, but once it is done, I've found that it works pretty well.
[1] https://github.com/MichalLytek/type-graphql/
[2] https://graphql-code-generator.com/
First transform the JSON into proper class-objects and then validate the object for correctnes.
IMHO this has nothing to do with TypeScript because in your runtime all the type information is gone and you need to validate the remote data never the less. TypeScript can help you structure your validations but you need to write them.
[1] https://github.com/typestack/class-transformer
[2] https://github.com/typestack/class-validator
For me, the duplicated types turned out to be less of a hassle than initially anticipated. In many cases, I ended up with different data structures anyway because the data is transformed in the client. I think of them as transport types and client types.
[1]: https://github.com/gcanti/tcomb-validation
https://graphql-code-generator.com/