The benefits of having a type safety api with nodejs backend ?
In production environments, the primary objective is to maintain stability and prevent issues. One common approach for achieving this is by minimizing code modifications. However, when introducing new features or building products, this method may not be ideal. To minimize potential risks, teams use various techniques such as tests and architectural design. In this article, I'll discuss one specific tool: type-safe APIs.
In a modern web application with a frontend and backend built using technologies like React or other frontend frameworks, having both the frontend and backend written in JavaScript offers several advantages. One significant advantage is the ease of implementing a type-safe API.
Type-safe APIs enhance developer experience by reducing errors through compile-time checks, improving code readability with clear type annotations, and supporting better tooling for autocompletion and refactoring. This leads to more maintainable code, efficient collaboration, and a smoother development process.
To create a route for our backend, we first define a DTO (Data Transfer Object) to send data. Next, we develop a service in the frontend to call this data or send it. In the frontend, we specify the route and create a clone of the backend DTO to send. I often feel as though I'm repeating myself.
A good starting point for implementing API safety is by sharing the route path and DTO types between the backend and frontend within a monorepo (single repository). This method does not require any external libraries or tools; it only involves your frontend and backend.
For example:
In /lib/routes.ts
const routes = { posts: "/api/posts" }
In /lib/createPostDto.ts
type CreatePostDto = { title: string; content: string; date: Date; // ... }
Your server route can be configured as follows.
In /apps/server/post/routes.ts
import createPostDto from 'lib/createPostDto'; import routes from 'lib/routes'; app.post(routes.posts, (req: Request<CreatePostDto>, res: Response) => { // Your route handling logic here });
In /apps/client/service/post.ts
async function createPost(postToCreate: CreatePostDto): Promise<void> { const response = await fetch(routes.posts, { method: "POST", body: JSON.stringify(postToCreate), headers: { "Content-Type": "application/json" }, }); if (!response.ok) throw new Error(`HTTP error ${response.status}`); const json = await response.json(); return json; }
This is a good initial step to maintain a type-safe API, but setting up the type-safe API fully still requires additional configuration. The next steps include using tools like trpc or ts-rest, which are excellent for creating type-safe APIs in TypeScript. TS-Rest enables you to maintain a REST API while also providing advantages when required.
However, there may be instances where Node.js is not the preferred choice for the backend. In such cases, alternatives like generating an OpenAPI JSON file from a backend framework (e.g., using tools like openapi-generator) and setting up a frontend service can be considered. Additionally, frameworks like Hono offer type safety within their frameworks.
In conclusion, when feasible, having both a frontend and backend written in JavaScript for a web application offers excellent developer experience and maintainability in the long term. Furthermore, tools like TS-Rest can be easily integrated into popular frameworks such as Nest.js or Next.js.