Effortless Typescript Type Sharing Between React and Node.js

Typescript Type Sharing Between React and Node.js Node.js

Effortless Typescript Type Sharing Between React and Node.js

Are you seeking a hassle-free method to share Typescript types between projects without additional complexities? Look no further! In this blog post, I’ll walk you through a streamlined approach that will save you time and effort while supercharging your REST API development. You don’t need any additional tooling, frameworks, or libraries.

Many blog posts cover this topic, but most involve additional tooling, which can be overwhelming. I’m excited to share a much simpler and more efficient technique that will make your life easier. Say goodbye to unnecessary complexities and embrace a more straightforward way to handle Typescript types. I already created a twitter thread on this topic, but thought it would good idea to turn it into a blog post. Let’s dive in together! 🏊‍♀️

Perhaps, you’re already familiar with schema validation libraries that you can use to validate request parameters and responses. Most of these libraries are using JSON.parse static function. The problem is that JSON.parse is a blocking I/O operation that blocks Node.js main loop. If you have big payloads or need to validate large objects, you can have performance issues.

profiling node.js

Profiling node.js with Zod

If you use GraphQL or Swagger, sharing API types between client and server is pretty easy. For example, GraphQL has a tool graphql-codegen that is very easy to configure and allows generating types by running npm command.


overwrite: true
schema:
  - https://graphql.contentful.com/content/v1/spaces/${CONTENTFUL_SPACE_ID}:
      headers:
        Authorization: "Bearer ${CONTENTFUL_ACCESS_TOKEN}"
generates:
  __generated__/contentful.ts:
    plugins:
      - typescript

codegen.yml

"scripts": {
    "codegen": "graphql-codegen --require dotenv/config --config codegen.yml dotenv_config_path=.env.local"
},

package.json

How To use Typescript Types for REST API

Even if you’re a fan of REST APIs and prefer not to rely on any tooling for documentation, I have a step-by-step guide to type your REST API with ease. If you use Express, you can use @types/express and extend its Request and Response types. You can type query parameters, body parameters and responses for your API.


import type { Request, Response } from 'express'
import type { ParsedQs } from 'qs'

import type { ResponseError, ResponseSuccess } from '../../api-types'

import { editRoute } from './serviceRoutes'
import type { EditRouteBodyParams } from './types'

export const deviceController = {
  editRoute: async (
    req: Request<any, any, EditRouteBodyParams, ParsedQs, Record<string, any>>,
    res: Response<ResponseSuccess | ResponseError>
  ) => {
    const editResponse = await editRoute({ ...req.body })
      if (editResponse instanceof Error) {
        return res.status(500).json({ error: editResponse.message })
      }
      return res.send({ message: `Route with name: ${req.body.name} was updated` })
  },
} as const

src/modules/devices/controllerDevice.ts

export interface Route {
  timestamp: string
  name: string
  geojsonString: string
}

export type AddRouteBodyParams = Pick<Route, 'name' | 'geojsonString'>

export interface DeleteRouteBodyParams {
  deviceId: string
  timestamp: string
}

export interface EditRouteBodyParams {
  deviceId: string
  timestamp: string
  name: string
  geojsonString: string
}

src/modules/devices/types.ts

Organizing Types

The goal is to import all created types into a single file. It’s better to organize node.js code into modules where each module has types.ts with all module’s types. This way you can import all types into a single file.


export interface ResponseSuccess {
  message: string
}

export interface ResponseError {
  error: string
}

import * as middlewareTypes from './middleware/types'
import * as authTypes from './modules/auth/types'
import * as deployTypes from './modules/deploy/types'
import * as missionsTypes from './modules/missions/types'
import * as routesTypes from './modules/routes/types'
import * as sitesTypes from './modules/sites/types'

src/api-types.ts

Exporting Types

Now, we need to be able to export all types into a single file. We can achieve this by creating a separate Typescript configuration file that we use only for generating our types for another projects. The key is outFile option in Typescript configuration.


{
  "compilerOptions": {
      "target": "es2020",
      "module": "commonjs",
      "moduleResolution": "node",
      "allowJs": true,
      "checkJs": false,
      "noUncheckedIndexedAccess": true,
      "useUnknownInCatchVariables": true,
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "lib": ["es2020"],
      "declaration": true,
      "emitDeclarationOnly": true,
      "outFile": "dist/export-api-types.ts"
  },
  "files": [
      "./src/api-types.ts"
  ]
}

tsconfig-export.json

Then, we just add npm command in our Node.js project that exports types into single file and copy them into the front-end or whatever project.


"scripts": {
    "generate-types": "tsc -p tsconfig-export.json && cp ./dist/export-api-types.d.ts ../client_map_app/src/types"
},

package.json

Conclusion

In this blog post, we’ve explored a hassle-free method to share Typescript types between React and Node.js projects. Remember, organizing your Node.js modules efficiently is key to maintaining a clean and maintainable codebase. Embrace the power of Typescript to build robust and scalable applications 🙂

Happy coding, and may your projects flourish with Typescript success!

NEED A SOFTWARE ARCHITECT? LET’S BUILD SOMETHING.

GET IN TOUCH

Leave a Reply

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

%d bloggers like this: