How to Share Types Between Frontend and Backend Apps

Effectively sharing types between frontend and backend apps is a very needed solution. It eliminates bugs and breaking, speeds up integrations, and ensures your APIs can efficiently communicate even as you continue to develop.

Yet, types are typically coupled to a codebase. This means it used to be next to impossible to effectively share and maintain types between codebases.

In this tutorial you will solve the problem altogether by creating and working with “entity components” to share types and easily ensure your backend and frontend can always communicate with each other without breaking.

In fact, entity components can do much more — they can help speed up integrations for example. Easy type sharing is juse one advatange.

This tutorial is based on this great example by Nitsan Cohen and will show the code and steps created in Nitsan’ tutorial. You can follow this post with some extra curation or the original one as you choose.

Let’s share some types!

Sharing types: A real use-case

Let’s explore a real-world example of sharing types.

Imagine being a Mom or a Dad with children. If you’re like Nitsan, you have 5 of them to be exact.

Each child needs a certain amount of your time allocated to help with their homework. Each of them expects something different. Yet you only have one unit of time a day (e.g. an hour) to split between them.

Your children — the “clients”- expect type definition to be like (using dad in the example but please use mom or anything that works for you):

export type Dad = {
name: string;
age: number;
children: number;
spendWithChild?: (childAge: number) => number;
};

Yet the service (parent) here only uses this type of data as output:

type Dad = {
name: string;
age: number;
children: number;
spendWithChild: 20; // 20 minutes a day
};

Oh oh… there’s a problem!

Your children expect an exponential number based on their age, while I returned a constant time of 20 minutes per child.

In the end, your contracts align and you agreed to share the same type:

export type Dad = {
name: string;
age: number;
children: number;
spendWithChild: (children: number) => 20; // Should always return 20 minutes per child :)
};

Let’s share some types!

Ok time to see how to start sharing types.

The key is to create “entity components” — a component that can manipulate data as well (by using ES6 classes). As a result, you get a standardized way to communicate and manipulate the object, and even keep your code clean.

It’s a very fast and effective way to do it. And later, when there’s a change, you can easily manage it, control versions, and ensure nothing breaks.

To create such components we’ll use open-source tool Bit — that makes it easy to build anything as a modular, independent, and versioned component.

Setting Up a Workspace

This guide assumes that you have a bit.cloud account and know how to open a remote scope. If not, go ahead and take half a minute (and it’s free).

Then install Bit:

npx @teambit/bvm install

Next create a Bit Workspace. This is where you build, compose, and manage components. We won’t dive into it now, but you’ll soon feel the experience.

Not that the Workspace does not “store” components — they are not coupled to it in any way.

$ bit init

In the root of your workspace, two files and one directory will be created:

  • workspace.jsonc: This file contains the configuration of the Workspace.
  • .bitmap: Here Bit keeps track of which components are in your workspace. You shouldn't edit this file directly.
  • .bit: This is where the components objects are stored.

Here is what the Workspace looks like:

  • Please ensure you replace "defaultScope": "[your-bit-cloud-username].[scope-name]" with your Bit Cloud user name and the scope name.

Developing the frontend and backend components

Start by creating the backend component using the create command, which generates components from component templates. Component templates give you boilerplate code and folder structure for new components.:

$ bit create express-app backend/server

We’ll need to add this component to our workspace.json file since it's of application type. Components can be built, served, and deployed using application types. There is a build strategy for each application type (Node, React etc.) for development and production, as well as a deployment solution for each application type.

Add this line to the top level of your workspace.json file. This component will be added to your Workspace as an extension.

{
...
"nitsan770.shared-types/backend/server": {},
...
}

Don’t forget to replace Nitsan’s user and scope names with yours.

bit status is a useful command that tells you what's the status of components in your workspace. Let's run it to see if everything is okay:

$ bit status

Here’s the output:

new components
(use "bit tag --all [version]" to lock a version with all your changes)
     > backend/server ...  issues found
missing packages or links from node_modules to the source (run "bit install" to fix both issues. if it's an external package, make sure it's added as a package dependency):
route.ts -> express (missing package)
server.app-root.ts-> express (missing package)

As you can you are missing the express package . You can install it in the Workspace using the following command. You can also install CORS to enable it on your server.

$ bit install express cors

Let’s modify the mock-route file of our component:

import type { Route } from './route';
type Dad = {
name: string;
age: number;
children: number;
spendWithChild: 20;
};
export function getDaddyRoute(): Route {
const dad: Dad = {
name: 'Nitsan Cohen',
age: 33,
children: 5,
spendWithChild: 20,
};
return {
method: 'get',
route: '/spend-time-with-daddy',
middlewares: [async (req, res) => res.send(dad)],
};
}

And add CORS headers to the response:

import Application from 'express';
import cors from 'cors';
import { getPort } from './get-port';
import { getDadRoute } from './mock-route';
export const expressApp = async () => {
const app = Application();
app.use(cors());
const port = await getPort();
registerRoutes(app);
app.listen(port, () => {
console.log(`server app listening on port ${port}`);
});
};
function registerRoutes(app: Application.Application) {
const mockRoute = getDadRoute();
const routes = [mockRoute];
routes.forEach((route) => {
app[route.method](route.route, route.middlewares);
});
}
expressApp();

It’s time to run our server-side component! Run the following command:

$ bit run server

You should see the following output in your terminal:

server app listening on port 3000
server app is running on http://localhost:3000

If you go to your browser and visit http://localhost:3000/spend-time-with-daddy, you should see the following output:

{
"name": "Nitsan Cohen",
"age": 33,
"children": 5,
"spendWithChildren": 20
}

Now let’s do the same for the frontend component. First create it from the react-app template:

$ bit create react-app frontend/fetch-dad

Then add it to our workspace.json file:

{
...
"nitsan770.shared-types/backend/server": {},
"nitsan770.shared-types/frontend/fetch-daddy": {},
...
}

Now let’s modify the app.tsx file to fetch the Dad data from our running server:

import React, { useEffect } from 'react';
type Dad = {
name: string;
age: number;
children: number;
spendWithChild?: (childAge: number) => number;
};
export function FetchDadApp() {
const [dad, setDad] = React.useState<Dad | null>(null);
useEffect(() => {
fetch('http://localhost:3001/spend-time-with-daddy')
.then((res) => res.json())
.then((data) => setDad(data));
}, []);
  return (
<>
<div>
The time you have with your Dad is:
{dad?.spendWithChild(7)} minutes.
</div>
</>
);
}

Next run your frontend component app! Run the following command:

$ bit run fetch-Dad

You will see the following error when you open the app in the browser:

That’s exactly what I told my little child: Dad.spendWithChild isn't a function!

A literal dad joke doesn’t come often, take a moment to appreciate it.

In the backend, the spendWithChild property is set to a number. In the frontend app, spendWithChild is considered a function.

Obviously, you’ll get an error if you try to invoke an integer.

Well, you already know what the solution is. A shared entity (type) component!

Creating the shared entity (type) component

First you’ll have to add teambit.community/envs/community-react as an extension to the Workspace, since you are going to use a component template that is not part of the Bit's core ecosystem (btw, you can add your own component templates).

Add the following line to your workspace.json file:

{
"nitsan770.shared-types/backend/server": {},
"nitsan770.shared-types/frontend/fetch-dad": {},
"teambit.community/envs/community-react@2.1.3": {},
...
}

Now run the following commands to install the extension:

bit install && bit compile

If you run bit templates, you will see that my-entity template also appears(right at the bottom):

The following template(s) are available with the command bit create:
Example - bit create <template-name> <component-name>
teambit.generator/generator
component-generator (create your own component generator
Docs: https://bit.dev/docs/dev-services-overview/generator/generate-component)
workspace-generator (create your own workspace generator -
Docs: https://bit.dev/docs/dev-services-overview/generator/generate-workspace)
teambit.pipelines/builder
build-task (create a custom build task for your component pipelines)
teambit.react/react
react (a basic react component)
react-context (a react context component)
react-hook (a react hook component)
react-js (a basic react component in js)
react-env (customize the base React env with your configs and tools)
teambit.harmony/aspect
aspect (extend Bit capabilities)
teambit.html/html
html-env (customize the base Html env with your configs and tools)
html (a basic html component)
teambit.harmony/node
node-env (customize the base Node env with your configs and tools)
node (a Node.js module)
express-app (a bit express application)
express-route (an express route)
teambit.react/react-native
react-native-env (customize the base React Native env with your configs and tools)
react-native (a basic react native component)
teambit.mdx/mdx
mdx (MDX-file compiled by Bit to a reuseable component)
teambit.community/envs/community-react@2.1.3
my-react (react components with figma embed and scss)
my-entity (entity component) <-- this is the one we are looking for

Let’s create the shared entity (type) component:

$ bit creat emy-entity entities/dad

We are referring to this component as an entity, not a type. The main difference is that we are creating a component that can manipulate data as well (by using ES6 classes). As a result, we have a standardized way to manipulate the object and end up with cleaner frontend and backend code.

Additionally, if someone needs to manipulate data, they can update the entity component and anyone else who needs it afterward will also benefit (since they won’t have to repeat the logic).

Let’s have a look at the entity component:

export type DadProps = {
name: string;
age: number;
children: number;
readonly spendWithChild?: number;
};
export class Dad implements DadProps {
constructor(
readonly name: DadProps['name'],
readonly age: DadProps['age'],
readonly children: DadProps['children']
) {}
  get spendWithChild() {
return 100 / this.children;
}
  static fromObject(plainDad: DadProps) {
return new Dad(plainDad.name, plainDad.age, plainDad.children);
}
  toObject() {
return {
name: this.name,
age: this.age,
children: this.children,
};
}
}

spendWithChild is now a getter method that returns the number of minutes Dad spends with his children.

We can then use the dad.spendWithChild in our frontend component and enjoy a much cleaner codebase:

import React, { useEffect } from 'react';
import { Dad } from '@nitsan770/shared-types.entities.dad';
export function FetchDadApp() {
const [dad, setDad] = React.useState<Dad | null>(null);
useEffect(() => {
fetch('http://localhost:3001/spend-time-with-daddy')
.then((res) => res.json())
.then((data) => setDad(data));
}, []);
  return (
<>
<div>
The time you have with your Dad is:
{dad?.spendWithChild} minutes
</div>
</>
);
}

Also, we have to add the new entity to our backend:

import type { Route } from './route';
import { Dad } from '@nitsan770/shared-types.entities.Dad';
export function getDadRoute(): Route {
const dad: Dad = Dad.fromObject({
name: 'Nitsan Cohen',
age: 33,
children: 5,
});
return {
method: 'get',
route: '/spend-time-with-daddy',
middlewares: [async (req, res) => res.send(dad)],
};
}

Mocked Dad

Now that we have a shared entity component for the frontend and backend, we can mock it.

There are a few benefits to having the mock in the entity component:

  • It is no longer necessary for developers to mock their test/rendering data when building features around these entities.
  • Mocks allow you to add tests to entity components, so API changes will be more easily visible. There is no need to wait for something to break in other teams to figure out what went wrong.

Here’s how to mock the entity component:

import { DadProps } from './Dad';
export const mockDad: DadProps = {
name: 'Nitsan Cohen',
age: 33,
children: 5,
};

Let’s add a test for the entity component:

import { Dad } from './Dad';
import { mockDad } from './Dad.mock';
describe('Dad', () => {
it('should not spend more than 20 minutes per child', () => {
const Dad = Dad.fromObject(mockDad);
expect(Dad.spendWithChildren).toBeLessThanOrEqual(60);
});
});

If you run the test, you’ll see that the entity component works:

$ bit test

The test passed:

PASS  shared-types/entities/Dad/Dad.spec.ts
Dad
✓ should not spend more than 60 minutes with the children (1 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.598 s, estimated 1 s
Ran all test suites.

In this way, if something breaks, we don’t release it, or can at least notify the entity consumer with the appropriate semi-version.

Another major benefit is the ability to export the mock and use it in backend and frontend tests. As a result, developers on other teams save a great deal of time.

Tag and export components

Now that you’ve developed your components it’s time to tag and release them to the community (or only to your organization if you want).

During the tagging process, your component will go through these steps:

  • Bit locks the current state of the component (like git commit, but for components instead of repositories).
  • The component tests will run and you’ll see the results.
  • Your component will be built and packaged so anyone can install it in their projects.
  • In app type components, the component will also be deployed wherever you define it.

Note that all of these phases are completely customizable and you can add any steps you want.

$ bit tag — message first release

You’ll see the following output:

new components
(first version for components)
> backend/server@0.0.1
> entities/dad@0.0.1
> frontend/fetch-dad@0.0.1

The next step is to export your components. Here we’ll export to bit.cloud. It’s free and easy. You can export them to your own server if you want.

$ bit export

Success!

exported the following 3 component(s):
nitsan770.shared-types/backend/server
nitsan770.shared-types/entities/dad
nitsan770.shared-types/frontend/fetch-Dad

The components are now on bit.cloud. You can install them with any package manager or share them with your team.

Managing dependencies

The real beauty about sharing this type using Bit is the ease of managing dependencies.

Imagine that later on a new child was added to the family. To keep the sum on 20 minutes, we would like to update the spendWithChild getter method.

export type DadProps = {
name: string;
age: number;
children: number;
readonly spendWithChild?: number;
};
export class Dad implements DadProps {
constructor(
readonly name: DadProps['name'],
readonly age: DadProps['age'],
readonly children: DadProps['children']
) {}
  get spendWithChild() {
return 120 / this.children; // 20 minutes per child
}
  static fromObject(plainDad: DadProps) {
return new Dad(plainDad.name, plainDad.age, plainDad.children);
}
  toObject() {
return {
name: this.name,
age: this.age,
children: this.children,
};
}
}

Let’s tag the component with a new version:

$ bit tag — message refactor spendWithChildren

And here’s the output of the tag:

changed components
(components that got a version bump)
> nitsan770.shared-types/entities/dad@0.0.2
auto-tagged dependents:
nitsan770.shared-types/backend/server@0.0.2
nitsan770.shared-types/frontend/fetch-dad@0.0.2

We didn’t change anything on the frontend or backend. How did they get tagged? The components were automatically tagged, if you look closely. Bit has detected these components are using the entity component as a dependency and since it was modified and tagged, so were the dependents.

Note that the auto tag process only works if all components are in the same workspace. But don’t worry. We’re launching Ripple soon. When you export a component to bit.cloud, Ripple will automatically detect which components are using it, and will also upgrade their versions (and notify you if something went wrong).

It would be possible to set updates to auto if it’s a minor update, and to manual if it’s a breaking change. As a result, you are always in control of your components and can ensure they are working properly.

Summary

We saw how easy it is to make a shared entity component that can be used in any project. Having a shared entity component between your frontend and backend has serveral powerful benefits:

  • It’s independent and has its own release cycle.
  • Make integration easier for developers.
  • A single source of truth about what an entity is.
  • Design-time integration with local feedback in the IDE as you integrate.
  • Updates on changes to the entity encapsulated in a component.
  • Consumers can test against mock data.
  • Provide types and interfaces for remote APIs.
  • Tests, documentation, usage examples.

Learn more


How to Share Types Between Frontend and Backend Apps was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Reply

Your email address will not be published. Required fields are marked *

Instagram

Why do People Say: "Developers are Lazy"?
The saying “work smart, not hard” is applicable for programmers.
.
https://hackernoon.com/why-do-people-say-developers-are-lazy

.
Author: Aga Wozniak
.
.
.
.
.
#blog #100Daysofcode #javascript #vuejs #datascientist #peoplewhocode #learntocode #coding #developerlife #frontenddeveloper #backenddeveloper #fullstackdeveloper #developer #webdeveloper #thedevlife #phpdeveloper #computerscience #programmer #programmingisfun #codingdays
...

Mitigating the DDOS Threats Facing Banks and Fintechs
As much as digitization and cyber simplified banking, the Fintech sector has left digital payment activity exposed to malicious and suspicious activity.
.
https://hackernoon.com/mitigating-the-ddos-threats-facing-banks-and-fintechs

.
Author: Josh Horowitz
.
.
.
.
.
#blog #100Daysofcode #javascript #vuejs #datascientist #peoplewhocode #learntocode #coding #developerlife #frontenddeveloper #backenddeveloper #fullstackdeveloper #developer #webdeveloper #thedevlife #phpdeveloper #computerscience #programmer #programmingisfun #codingdays
...

24 Best JavaScript Blogs and Websites
In this overview, we have compiled a list of popular sites, as well as JS blogs that are worth reading and keeping in your bookmarks.
.
https://hackernoon.com/24-best-javascript-blogs-and-websites

.
Author: natashatsybliyenko
.
.
.
.
.
#blog #100Daysofcode #javascript #vuejs #datascientist #peoplewhocode #learntocode #coding #developerlife #frontenddeveloper #backenddeveloper #fullstackdeveloper #developer #webdeveloper #thedevlife #phpdeveloper #computerscience #programmer #programmingisfun #codingdays
...

The Projects Working to Lower Ethereum Gas Fees
As more investors try their hand at DeFi, gas fees are shooting over the roof, making engaging with decentralized apps uneconomical for most users.
.
https://hackernoon.com/ethereum-gas-fees-are-there-any-projects-working-to-optimize-eth-gas-fees

.
Author: CryptoVirally SLR
.
.
.
.
.
#blog #100Daysofcode #javascript #vuejs #datascientist #peoplewhocode #learntocode #coding #developerlife #frontenddeveloper #backenddeveloper #fullstackdeveloper #developer #webdeveloper #thedevlife #phpdeveloper #computerscience #programmer #programmingisfun #codingdays
...

On the Edge of a New Year: IT Predictions for 2022
The single biggest cause of network errors are people.
.
https://hackernoon.com/an-interview-with-uplogix-ceo-lisa-frankovitch

.
Author: Mignonette Garnier
.
.
.
.
.
#blog #100Daysofcode #javascript #vuejs #datascientist #peoplewhocode #learntocode #coding #developerlife #frontenddeveloper #backenddeveloper #fullstackdeveloper #developer #webdeveloper #thedevlife #phpdeveloper #computerscience #programmer #programmingisfun #codingdays
...

How to Modernize IBM i Applications
If you’re like most IBM i users, you know how much value your IBM i data and applications bring to your business. Your end-users, however, may not. In today’s world of rich user experience, fast-paced application development, and constantly evolving customer expectations, IBM i applications are unde…
.
https://hackernoon.com/how-to-modernize-ibm-i-applications

.
Author: Lansa
.
.
.
.
.
#blog #100Daysofcode #javascript #vuejs #datascientist #peoplewhocode #learntocode #coding #developerlife #frontenddeveloper #backenddeveloper #fullstackdeveloper #developer #webdeveloper #thedevlife #phpdeveloper #computerscience #programmer #programmingisfun #codingdays
...