Stay in Sync: Share TypeScript Types between Separate Repositories

Sharing TypeScript Types between Backend and Frontend ReposKeeping backend and client in sync with shared types and BitImage by Hafis Fox from PixabayDealing with a common data model between the front-end and the back-end can be a pain if you’re not wo…

Sharing TypeScript Types between Backend and Frontend Repos

Keeping backend and client in sync with shared types and Bit

Image by Hafis Fox from Pixabay

Dealing with a common data model between the front-end and the back-end can be a pain if you’re not working on a monorepo. This is because ideally, you do not want duplicate code that can become out of synch by lack of maintenance.

If the data model changes on the back-end, you need to have a surefire way of making sure that the front-end (or rather, let’s call it client) code will notice the change. Otherwise the latter will suffer greatly either by receiving no-longer-compatible payloads, or it’s just ignoring the new additions. In either case, this is not what you want.

So let’s take a look at some potential strategies when it comes to keeping client and backend code in synch with each other.

Extracting common models into 3rd party dependencies

Working under the assumption that you have a multi-repo setup, once you identify a data model (or a set of data models) that needs to be used both by the server and its clients (whether they’re user-facing apps or other services is irrelevant here), then it might be a good idea to extract these models into a common library.

By going down this route, you’re making sure that both the server and the client import and use the exact same data model.

What’s good about this? They will both know exactly what the other is sending. They’ll both know, at the exact same time, when something changes. You’ve essentially leveled the playground for both parties.

That’s great.

What’s wrong with it then?

Well, for starters, since we’re working on a JavaScript-Node.js assumption here, you’d most likely be required to turn this new repository into yet another NPM dependency. It’s not a big hustle, but it’s also extra work. And most importantly, you’ve also created a new repository, but who’s in charge of it? Is it the back-end? What if one of the client projects needs to make a change on the model, how will that be handled? If you’re working on a multi-project multi-team environment — which is a very common scenario — you might have just introduced an organizational nightmare.

This solution is best suited for scenarios where you have one team working on everything and owning all codebase.

A word of caution: When doing this, make sure that what you turn into a common 3rd party module is the DTO (Data Transfer Object) which only contains the fields used to transfer data between one entity and the other. Because this can take the form of a class, we might be also tempted to share methods, which include — what should be — private business logic. This is a big no-no, so be very careful when doing this.

It’s one thing to share a few fields and a very different one is to share business logic, which could potentially be private knowledge the company is not interested in sharing.

Type sharing with TypeScript and Bit

https://bit.dev/deleteman/quotes-lister

If you’re also using TyepScript, you’ll want to make sure you share more than just the “shape” of the payload but rather the types as well.

In this case, we can take it a step further, and instead of simply creating a new repository and turning it into yet another NPM dependency we can use Bit.

If you haven’t heard of it yet, Bit is an open-source tool (with native integration to Bit Cloud remote hosting platform) that helps you create and share independent components. These are components (or modules) that are independently developed, versioned, and collaborated on.

You can either author new independent components from scratch or gradually extract components from an existing codebase (which is kinda what we’re discussing here).

While that sounds an awful lot like NPM, there are some major differences:

  • You don’t have to physically extract the code to version it, share it, and collaborate on it, independently. You can ‘export’ a component directly from inside your repository. Bit allows you to identify a section of your code as a component and treat it independently from that point on. This, in turn, helps you simplify the sharing process since you don’t have to set up a separate repo and rework the way you import those files into your project.
  • People ‘importing’ your components (as opposed to just installing them) can also collaborate on them, modify them, and export them back to their ‘remote scope’ (remote component hosting).
    This is incredibly powerful if you’re working as a group of teams inside the same organization because you’re able to collaborate on the same tool across teams without having to work on a separate project to do it. Importing a Bit component downloads the code and copies it into your working directory. It also generates a corresponding package in your node_modules directory. This package is regenerated as you change the source code (in the working directory). That way you’re able to consume it using an absolute path that works in all contexts (and will even work if you choose to install the component’s package without importing it).

And that is what we’re going to be taking advantage of, the fact that you can turn a local setup into a multi-repo situation without worrying too much about anything really.

And when I say “multi-repo” I should’ve used quotes because you’re not going to worry about creating and maintaining a new repository, you can simply turn a section of your base code into an independent component, version it using Bit, and it will be published on a centralized location for you.

With that, you can then import the new component, with the shared types, anywhere you want. That means as many client-applications as you want, or any other microservice as well, they will all import the type definition as an external dependency, but you did not have to do anything around NPM or Github to provide it.

As an added bonus they could potentially suggest and provide new changes to the code. This is again, without you having to worry about setting up a repo or anything. This would all be handled through Bit.

Finally, since you’re only sharing type definitions, you do not have to worry about sharing business logic like before.

A quick example

Given how easy it is to build a quick working example with Bit, I’ve gone ahead and created a very simple project to illustrate this.

The project is called “Quote Lister” and you can see it here on bit or here directly on Github. The logic behind it is very simple, you have:

  • A service in Node.js serving a list of quotes taken from a text file.
  • A React component requesting those quotes and listing them on the screen.

As you can probably imagine, both components could use a common type to describe the structure of the quotes, and here is where the shared type comes into play. I’ll also create a new component that only exports the common types and I’ll have both the service and the react component import it.

If you want to know exactly the steps I took to create this project, you can check out this other article I wrote which gives you the step-by-step details.

While the actual implementation details for both, backend and frontend are not relevant, check out the following screenshot showing the most interesting part of both:

Look closely, on the left you have the React component, and on the right you have the Node.js backend. They’re very different code bases, but they both install the same library: @deleteman/quotes-lister.shared-types

Here’s the kicker: that is not an external library, however, thanks to Bit’s magic, it can be used as such.

Take a look at my project’s structure:

It’s all local, but because these are all components managed by Bit, they’re also auto-referenced from within my node_modules folder under my Bit username (@deleteman). This allows me to use the shared library between projects without having to worry about first publishing it into npm, and moving it into another repo. In fact, if my code was more generic and you only wanted to use only one or maybe two of these projects, you could simply install them through Bit or even NPM individually.

The bit.dev platform shows you how to import my components into your project

The BFF pattern

When it comes to keeping client and server code in sync we normally tend to assume we’re going to be sharing that code. However, through the use of the BFF pattern, we can avoid that hustle and everything that it involves. Of course, this solution is not without its cons as well.

The BFF pattern is a way for you to create a common interface between the server and the client without having to share code between projects. How is this achieved then? Through the creation of a proxy service that acts as a mapper between both data models.

That’s right, the BFF pattern, also known as Backend for Frontend pattern revolves around the creation of a proxy service that takes care of mapping the data models of both, the client and server. Thus allowing both of them to work with seemingly different models as long as they’re compatible.

Through this, you’re able to avoid the problems of sharing external dependencies or type definitions between client and server, yes. However, you’re also adding more services just to accommodate for a standardization problem.

Is this a good solution? As always, the answer is: it depends. If you’re only working with a single client application, you might use this pattern to give both parties a lot more wiggle room when it comes to their payload definitions. And maybe then the solution makes sense.

If on the other hand, you’re in a situation where you have multiple different clients, this might be a good way to transition between service versions with minimum client impact. Essentially, would add the proxy without them knowing, update the service version and as long as it’s backward compatible, you would be able to use the proxy to map whatever has changed on your payload on this new version. Once you’ve tested the new service and you’re confident it will hold, you can proceed to request clients to update their code.

Through the BFF pattern you’re keeping client and API code in sync by keeping them out of sync.

The perils of sharing too much

Finally, a little word of advice regarding this problem we’re trying to solve here.

There are two main problems when it comes to keeping client and server code in sync because you’re trying to stick to the DRY principle:

  1. You might overshare. We already cover this point: instead of simply sharing a basic payload definition that includes field names, and potentially, types for each field, you end up sharing a whole class with methods. I know it’s tempting but that only makes sense if the logic in those methods can be used in both places, otherwise you’re sharing internal and — most importantly — private logic with the client. And if the client is an outside party (not another app within your organization), you’re giving away your company’s IP. Do I need to say more?
  2. The more code you share the more coupled both systems become. It’s counterintuitive I know, but the extra effort you’re going through to share that code and keep yourself from copy&pasting files between projects, is causing you to couple the server and the client implementation. Think about it, if you’ve implemented the first solution, the minute your server needs to make a breaking change on the model, all clients need to be updated.

How can you avoid these problems then? The first one is easy: refrain from sharing code as part of your data models. If there is common code to be shared, do it somewhere else and with that specific intention only.

As for the second problem, there is no way around it. It becomes more of a problem of acceptance: given your current context, do you consider the coupling between client and server a problem? Depending on your answer, you’ll know how to proceed.

Having the same data model between client and server is a very common situation, sadly there is no right answer on how to approach it. Either share the code somehow, copy&paste files around or create yet another service to map things between the two parties.

It’s really up to you and your context to decide which one is the best solution.

What about you? Have you solved this problem differently? Share your experiences in the comments so we can all learn from each other!

Learn More


Stay in Sync: Share TypeScript Types between Separate Repositories was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


Print Share Comment Cite Upload Translate
APA
Fernando Doglio | Sciencx (2024-03-28T16:04:31+00:00) » Stay in Sync: Share TypeScript Types between Separate Repositories. Retrieved from https://www.scien.cx/2021/09/21/stay-in-sync-share-typescript-types-between-separate-repositories/.
MLA
" » Stay in Sync: Share TypeScript Types between Separate Repositories." Fernando Doglio | Sciencx - Tuesday September 21, 2021, https://www.scien.cx/2021/09/21/stay-in-sync-share-typescript-types-between-separate-repositories/
HARVARD
Fernando Doglio | Sciencx Tuesday September 21, 2021 » Stay in Sync: Share TypeScript Types between Separate Repositories., viewed 2024-03-28T16:04:31+00:00,<https://www.scien.cx/2021/09/21/stay-in-sync-share-typescript-types-between-separate-repositories/>
VANCOUVER
Fernando Doglio | Sciencx - » Stay in Sync: Share TypeScript Types between Separate Repositories. [Internet]. [Accessed 2024-03-28T16:04:31+00:00]. Available from: https://www.scien.cx/2021/09/21/stay-in-sync-share-typescript-types-between-separate-repositories/
CHICAGO
" » Stay in Sync: Share TypeScript Types between Separate Repositories." Fernando Doglio | Sciencx - Accessed 2024-03-28T16:04:31+00:00. https://www.scien.cx/2021/09/21/stay-in-sync-share-typescript-types-between-separate-repositories/
IEEE
" » Stay in Sync: Share TypeScript Types between Separate Repositories." Fernando Doglio | Sciencx [Online]. Available: https://www.scien.cx/2021/09/21/stay-in-sync-share-typescript-types-between-separate-repositories/. [Accessed: 2024-03-28T16:04:31+00:00]
rf:citation
» Stay in Sync: Share TypeScript Types between Separate Repositories | Fernando Doglio | Sciencx | https://www.scien.cx/2021/09/21/stay-in-sync-share-typescript-types-between-separate-repositories/ | 2024-03-28T16:04:31+00:00
https://github.com/addpipe/simple-recorderjs-demo