TypeScript introduces static typing to JavaScript. This means that variables, function parameters, and return values can have explicit types assigned to them. For example:
// Function with typed parameters and return value
function add(a: number, b: number): number {
return a + b;
}
const result = add(1, 2);
In an API context, static typing ensures that clients use the API correctly. If a client tries to pass a non - number to the add
function, TypeScript will raise a compilation error.
Interfaces in TypeScript are used to define the structure of an object. They can be used to describe the shape of data that an API expects or returns.
// Define an interface for a user object
interface User {
id: number;
name: string;
email: string;
}
// Function that takes a User object as a parameter
function printUser(user: User) {
console.log(`ID: ${user.id}, Name: ${user.name}, Email: ${user.email}`);
}
Enums are a way to define a set of named constants. They can be useful in an API to restrict the possible values of a parameter.
// Define an enum for user roles
enum UserRole {
Admin = 'ADMIN',
User = 'USER',
Guest = 'GUEST'
}
// Function that takes a UserRole as a parameter
function assignRole(role: UserRole) {
console.log(`Assigned role: ${role}`);
}
When building an API with TypeScript, you can use a framework like Express.js. Here is an example of defining a simple API endpoint using TypeScript and Express:
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
// Define an interface for the response data
interface GreetingResponse {
message: string;
}
// Define an API endpoint
app.get('/greet', (req: Request, res: Response<GreetingResponse>) => {
const response: GreetingResponse = {
message: 'Hello, World!'
};
res.json(response);
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
You can use TypeScript to validate and type - check the input received by the API. For example, using the zod
library for schema validation:
import express, { Request, Response } from 'express';
import { z } from 'zod';
const app = express();
app.use(express.json());
// Define a schema for the input data
const userSchema = z.object({
name: z.string(),
age: z.number()
});
app.post('/users', (req: Request, res: Response) => {
const validationResult = userSchema.safeParse(req.body);
if (!validationResult.success) {
return res.status(400).json({ error: validationResult.error.message });
}
const user = validationResult.data;
res.status(201).json(user);
});
To ensure backward compatibility and manage changes over time, it is a common practice to version your API. You can do this by including the version number in the URL.
import express, { Request, Response } from 'express';
const app = express();
// Version 1 of the API
app.get('/v1/greet', (req: Request, res: Response) => {
res.json({ message: 'Hello from v1!' });
});
// Version 2 of the API
app.get('/v2/greet', (req: Request, res: Response) => {
res.json({ message: 'Hello from v2!' });
});
Proper error handling is essential in an API. You can create custom error classes in TypeScript and handle errors gracefully.
import express, { Request, Response, NextFunction } from 'express';
class CustomError extends Error {
constructor(public statusCode: number, message: string) {
super(message);
}
}
const app = express();
app.get('/error', (req: Request, res: Response, next: NextFunction) => {
try {
throw new CustomError(500, 'Something went wrong');
} catch (error) {
next(error);
}
});
// Error handling middleware
app.use((err: CustomError, req: Request, res: Response, next: NextFunction) => {
res.status(err.statusCode).json({ error: err.message });
});
Interfaces should have a single responsibility. Avoid creating large, monolithic interfaces. Instead, break them down into smaller, more focused interfaces.
// Bad practice
interface ComplexUser {
id: number;
name: string;
email: string;
address: string;
phone: string;
role: string;
permissions: string[];
}
// Good practice
interface UserIdentity {
id: number;
name: string;
email: string;
}
interface UserContact {
address: string;
phone: string;
}
interface UserRole {
role: string;
permissions: string[];
}
If a property in an API response or input should not be modified, mark it as read - only.
interface Product {
readonly id: number;
name: string;
price: number;
}
function getProduct(): Product {
return { id: 1, name: 'Product 1', price: 100 };
}
TypeScript provides a rich set of features that can greatly enhance the process of designing robust APIs. Static typing, interfaces, enums, and other concepts help catch errors early, improve code readability, and make the API more self - documenting. By following common practices such as versioning and proper error handling, and best practices like keeping interfaces simple and using read - only properties, you can build APIs that are reliable, maintainable, and easy to use.