5 Tools and Patterns for Typesafe APIs

Elevating API Reliability and Robustness through Typesafety

Ashan Fernando
Bits and Pieces

--

Type safety plays a crucial role in achieving reliability and robustness. It helps you catch errors early and avoid them by reusing interfaces.

This blog post will explore tools and design patterns instrumental in creating typesafe APIs.

Regardless of which tool or combination of tools you use to add type-safety to your APIs, it’s recommended Bit to share your types and schema validators as independent components that can be shared between repositories.

This is often referred to as the ‘entity component pattern’. A component shared between the API and its clients is used as the contract between the two sides.

The entity component provides the relevant types for the API’s data, utility functions to handle the data, and schema validation for error catching in runtime.

A dependents graph of an “entity component” that serves as the contract between the API and the clients. The entity component provides types for the API as well as schema validation. Updates to the entity component are propagated to its dependents.

For example, this component uses the Zod library to share types and schema validation between a lambda function that handles a form submission and its client the form.

The TLDR:

1. Zod: A TypeScript-First Schema Declaration Tool

Zod emerges as a TypeScript-first schema declaration and validation library, enabling developers to construct complex, nested object schemas with enhanced typesafety. These schemas play a dual role: they validate data at runtime and generate TypeScript types based on the schema.

Key Features of Zod:

Type Inference: Zod schemas are inherently typesafe, allowing TypeScript to infer types from the schema definitions. This feature eliminates the need for redundant type declarations, streamlining the development process.

Complex Nesting and Composition: Zod supports the creation of deeply nested schemas and the composition of existing schemas. This flexibility is crucial for modeling complex data structures commonly encountered in modern web applications.

Custom Validations: Beyond basic type checking, Zod allows for custom validation logic, allowing developers to implement sophisticated validation rules tailored to their specific needs.

Error Handling: Zod schemas produce detailed error messages on validation failure, which aids in debugging and improves the overall development experience.

Integration with Existing TypeScript Projects: Zod seamlessly integrates with existing TypeScript projects, enhancing their typesafety without significant refactoring.

Example:

import { z } from 'zod';

const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

app.post('/users', (req, res) => {
try {
const user: User = UserSchema.parse(req.body);
// Handle the validated user...
} catch (e) {
res.status(400).send(e.errors);
}
});

In this example, UserSchema ensures that user data conforms to the defined structure and types, thus enhancing API typesafety.

2. tRPC: Typesafe APIs without Schemas

tRPC is a groundbreaking tool that allows the development of typesafe APIs without schemas or code generation. It utilizes TypeScript’s inference capabilities to maintain type consistency across the client and server.

Key Features of tRPC:

End-to-End Typesafety: tRPC provides complete typesafety across the full stack. This means that any changes in the backend API are automatically reflected in the frontend, significantly reducing the risk of type mismatches.

No Schema or Code Generation: Unlike many other typesafe tools, tRPC operates without defining separate schemas or generating code, simplifying the development workflow.

Seamless Integration with TypeScript: tRPC is built with TypeScript in mind, offering seamless integration and taking full advantage of TypeScript’s powerful type system.

Procedure-Based API Structure: tRPC uses a procedure-based approach to API design, a natural fit for TypeScript’s function and type system, leading to more intuitive and maintainable code.

Automatic Type Inference: The library automatically infers types from your API procedures, reducing boilerplate and eliminating the need for manual type definitions for API responses and requests.

Flexible Querying and Mutations: tRPC supports queries, mutations, and subscriptions, providing flexibility in how data is fetched and manipulated.

Easy Error Handling: It offers a straightforward way to handle errors, making the API robust and reliable.

Framework Agnostic: While often used with React, tRPC is not limited to any specific frontend framework, making it versatile for various development scenarios.

Example:

// Server-side
import { createRouter } from 'trpc/server';

const userRouter = createRouter()
.query('getUser', {
input: z.number(),
resolve({ input }) {
// Retrieve and return the user...
},
});

// Client-side
import { trpc } from './trpc';

const userId = 1;
const user = trpc.user.getUser.useQuery(userId);

Here, tRPC guarantees that the client’s query is in sync with the server’s expected input type, ensuring end-to-end typesafety.

3. GraphQL: Typesafe Query Language

GraphQL, a powerful query language for APIs, inherently supports typesafety. It requires explicitly defining data types that can be queried, ensuring the API adheres to the specified schema.

Key Features of GraphQL:

Strongly Typed Schema: GraphQL APIs are defined by a strong type system. This schema is a contract between the client and the server, ensuring only valid data is exchanged.

Client-Specified Queries: Clients can request exactly what they need, making the data-fetching process more efficient.

Single Endpoint: Unlike REST APIs with multiple endpoints, GraphQL APIs typically expose a single endpoint, simplifying the API structure.

Real-Time Data with Subscriptions: GraphQL supports real-time data updates through subscriptions, enabling clients to maintain up-to-date data.

Introspective: GraphQL APIs are self-documenting. Tools can introspect the schema to provide helpful insights and auto-completion.

Declarative Data Fetching: It allows for declarative data fetching and can seamlessly aggregate data from multiple sources.

Example:

type User {id: ID!name: String!
}

type Query {
getUser(id: ID!): User
}

A GraphQL server, such as Apollo Server, enforces this schema, maintaining the API’s typesafety.

4. Fuse.js: Enhancing User Experience with Typesafe Search

Fuse.js, a potent yet lightweight fuzzy-search library, is primarily used on the client side. While not directly related to API typesafety, it enhances user experience when used alongside typesafe APIs by providing flexible search capabilities.

Key Features of Fuse.js:

Zero Dependencies: Fuse.js is standalone and does not rely on external libraries, ensuring a lightweight footprint.

Flexible and Customizable: Offers various options to customize the search, including threshold, distance, and location.

Typesafe with TypeScript: Works well with TypeScript, ensuring that the search keys and results are typesafe.

Simple API: Easy to use and integrate into existing projects, with a straightforward API that makes fuzzy searching a breeze.

Example:

import Fuse from 'fuse.js';

const books = [
{ title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
// Additional books...
];

const fuse = new Fuse(books, { keys: ['title', 'author'] });

const results = fuse.search('gatsby');

Fuse.js integrates smoothly with TypeScript, ensuring that search keys and results are typesafe.

5. Swagger/OpenAPI: Beyond API Documentation

Swagger, also known as OpenAPI, transcends its traditional role as an API documentation tool. It enforces typesafety in RESTful APIs by defining routes and their expected request and response structures.

Key Features of Swagger/OpenAPI:

Standardized API Design: Offers a standardized approach to designing RESTful APIs, making them more understandable and easier to use.

Automatic Documentation: Generates interactive API documentation, which helps understand and test the API.

Code Generation: Supports automatic generation of client libraries, server stubs, and API documentation from an OpenAPI Specification.

Interactive API Console: Provides an interactive API console for users to try out API calls directly from the documentation.

Example:

openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/user/{id}:
get:
summary: Get a user by ID
parameters:
- in: path
name: id
required: true
schema:
type: integer
responses:
'200':
description: A user object
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string

This YAML file meticulously defines the types for the user API, enabling tools like Swagger UI or Swagger Codegen to generate typesafe client libraries.

Summary

The following table summarizes each tool and its ideal use cases, helping developers choose the right tool based on their specific API development needs.

Incorporating typesafety into API development is not merely a best practice but a necessity in modern software development. Tools like Zod, tRPC, GraphQL, Swagger/OpenAPI, and the Entity Component Pattern provide a robust framework for building typesafe APIs. Leveraging these tools and patterns significantly reduces runtime errors, improves code quality, and enhances API maintainability.

The key to successful API development lies in the nuances, and mastering typesafety is among the most critical aspects to ensure success.

And there we have it. I hope you have found this useful. Thank you for reading!

Learn More

--

--