Fastify TypeScript Development
You are an expert in Fastify and TypeScript development with deep knowledge of building high-performance, type-safe APIs.
TypeScript General Guidelines
Basic Principles
- •Use English for all code and documentation
- •Always declare types for variables and functions (parameters and return values)
- •Avoid using
anytype - create necessary types instead - •Use JSDoc to document public classes and methods
- •Write concise, maintainable, and technically accurate code
- •Use functional and declarative programming patterns; avoid classes
- •Prefer iteration and modularization to adhere to DRY principles
Nomenclature
- •Use PascalCase for types and interfaces
- •Use camelCase for variables, functions, and methods
- •Use kebab-case for file and directory names
- •Use UPPERCASE for environment variables
- •Use descriptive variable names with auxiliary verbs:
isLoading,hasError,canDelete - •Start each function with a verb
Functions
- •Write short functions with a single purpose
- •Use arrow functions for simple operations
- •Use async/await consistently throughout the codebase
- •Use the RO-RO pattern (Receive an Object, Return an Object) for multiple parameters
Types and Interfaces
- •Prefer interfaces over types for object shapes
- •Avoid enums; use maps or const objects instead
- •Use Zod for runtime validation with inferred types
- •Use
readonlyfor immutable properties - •Use
import typefor type-only imports
Fastify-Specific Guidelines
Project Structure
code
src/
routes/
{resource}/
index.ts
handlers.ts
schemas.ts
plugins/
auth.ts
database.ts
cors.ts
services/
{domain}Service.ts
repositories/
{entity}Repository.ts
types/
index.ts
utils/
config/
app.ts
server.ts
Route Organization
- •Organize routes by resource/domain
- •Use route plugins for modular registration
- •Define schemas alongside route handlers
- •Use route prefixes for API versioning
typescript
import { FastifyPluginAsync } from 'fastify';
const usersRoutes: FastifyPluginAsync = async (fastify) => {
fastify.get('/', { schema: listUsersSchema }, listUsersHandler);
fastify.get('/:id', { schema: getUserSchema }, getUserHandler);
fastify.post('/', { schema: createUserSchema }, createUserHandler);
fastify.put('/:id', { schema: updateUserSchema }, updateUserHandler);
fastify.delete('/:id', { schema: deleteUserSchema }, deleteUserHandler);
};
export default usersRoutes;
Schema Validation with JSON Schema / Ajv
- •Define JSON schemas for all request/response validation
- •Use @sinclair/typebox for type-safe schema definitions
- •Leverage Fastify's built-in Ajv integration
typescript
import { Type, Static } from '@sinclair/typebox';
const UserSchema = Type.Object({
id: Type.String({ format: 'uuid' }),
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
createdAt: Type.String({ format: 'date-time' }),
});
type User = Static<typeof UserSchema>;
const createUserSchema = {
body: Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ format: 'email' }),
}),
response: {
201: UserSchema,
400: ErrorSchema,
},
};
Plugins and Decorators
- •Use plugins for shared functionality
- •Decorate Fastify instance with services and utilities
- •Register plugins with proper encapsulation
typescript
import fp from 'fastify-plugin';
const databasePlugin = fp(async (fastify) => {
const prisma = new PrismaClient();
await prisma.$connect();
fastify.decorate('prisma', prisma);
fastify.addHook('onClose', async () => {
await prisma.$disconnect();
});
});
export default databasePlugin;
Prisma Integration
- •Use Prisma as the ORM for database operations
- •Create repository classes for data access
- •Use transactions for complex operations
typescript
class UserRepository {
constructor(private prisma: PrismaClient) {}
async findById(id: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { id } });
}
async create(data: CreateUserInput): Promise<User> {
return this.prisma.user.create({ data });
}
}
Error Handling
- •Use Fastify's built-in error handling
- •Create custom error classes for domain errors
- •Return consistent error responses
typescript
import { FastifyError } from 'fastify';
class NotFoundError extends Error implements FastifyError {
code = 'NOT_FOUND';
statusCode = 404;
constructor(resource: string, id: string) {
super(`${resource} with id ${id} not found`);
this.name = 'NotFoundError';
}
}
// Global error handler
fastify.setErrorHandler((error, request, reply) => {
const statusCode = error.statusCode || 500;
reply.status(statusCode).send({
error: error.name,
message: error.message,
statusCode,
});
});
Testing with Jest
- •Write unit tests for services and handlers
- •Use integration tests for routes
- •Mock external dependencies
typescript
import { build } from '../app';
describe('Users API', () => {
let app: FastifyInstance;
beforeAll(async () => {
app = await build();
});
afterAll(async () => {
await app.close();
});
it('should list users', async () => {
const response = await app.inject({
method: 'GET',
url: '/api/users',
});
expect(response.statusCode).toBe(200);
expect(JSON.parse(response.payload)).toBeInstanceOf(Array);
});
});
Performance
- •Fastify is one of the fastest Node.js frameworks
- •Use schema validation for automatic serialization optimization
- •Enable logging only when needed in production
- •Use connection pooling for database connections
Security
- •Use @fastify/helmet for security headers
- •Implement rate limiting with @fastify/rate-limit
- •Use @fastify/cors for CORS configuration
- •Validate all inputs with JSON Schema
- •Use JWT for authentication with @fastify/jwt