Advanced Patterns and Enterprise Features
1. Dependency Injection and IoC Container Typing
| Library/Pattern | Approach | Type Safety | Use Case |
|---|---|---|---|
| InversifyJS | Decorator-based DI container - @injectable, @inject | Full type inference, constructor injection | Enterprise apps, testable architecture |
| TSyringe | Lightweight DI - Microsoft library | Decorator metadata, automatic resolution | Node.js services, Clean Architecture |
| TypeDI | Dependency injection for TypeScript/JavaScript | Service tokens, type-safe containers | TypeORM integration, modular apps |
| Manual DI Pattern | Constructor injection without container | Explicit types, no magic | Simple apps, avoid framework lock-in |
| Factory Pattern | Type-safe factories with generics | Return type inference | Object creation, polymorphism |
| Service Locator | Central registry for dependencies | Type-safe get methods | Legacy integration, runtime resolution |
Example: InversifyJS dependency injection
// Install: npm i inversify reflect-metadata
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
// types.ts - Define service identifiers
export const TYPES = {
Database: Symbol.for('Database'),
Logger: Symbol.for('Logger'),
UserService: Symbol.for('UserService')
};
// interfaces.ts
export interface ILogger {
log(message: string): void;
}
export interface IDatabase {
query(sql: string): Promise<any>;
}
export interface IUserService {
getUser(id: string): Promise<User>;
}
// implementations.ts
import { injectable, inject } from 'inversify';
import 'reflect-metadata';
@injectable()
export class ConsoleLogger implements ILogger {
log(message: string): void {
console.log(`[LOG] ${message}`);
}
}
@injectable()
export class PostgresDatabase implements IDatabase {
async query(sql: string): Promise<any> {
// Database implementation
return [];
}
}
@injectable()
export class UserService implements IUserService {
constructor(
@inject(TYPES.Database) private database: IDatabase,
@inject(TYPES.Logger) private logger: ILogger
) {}
async getUser(id: string): Promise<User> {
this.logger.log(`Fetching user ${id}`);
const result = await this.database.query(
`SELECT * FROM users WHERE id = '${id}'`
);
return result[0];
}
}
// container.ts - Configure DI container
import { Container } from 'inversify';
const container = new Container();
container.bind<ILogger>(TYPES.Logger).to(ConsoleLogger);
container.bind<IDatabase>(TYPES.Database).to(PostgresDatabase);
container.bind<IUserService>(TYPES.UserService).to(UserService);
export { container };
// Usage
import { container } from './container';
const userService = container.get<IUserService>(TYPES.UserService);
const user = await userService.getUser('123'); // Fully typed!
// Testing - easy to mock dependencies
const mockLogger: ILogger = { log: jest.fn() };
const mockDatabase: IDatabase = { query: jest.fn() };
const testContainer = new Container();
testContainer.bind<ILogger>(TYPES.Logger).toConstantValue(mockLogger);
testContainer.bind<IDatabase>(TYPES.Database).toConstantValue(mockDatabase);
Example: Manual DI pattern (no framework)
// Simple, explicit dependency injection
// Services
interface Logger {
log(message: string): void;
}
interface Database {
query<T>(sql: string): Promise<T[]>;
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(message);
}
}
class PostgresDB implements Database {
async query<T>(sql: string): Promise<T[]> {
// Implementation
return [] as T[];
}
}
// Service with constructor injection
class UserService {
constructor(
private readonly db: Database,
private readonly logger: Logger
) {}
async getUser(id: string): Promise<User> {
this.logger.log(`Fetching user ${id}`);
const [user] = await this.db.query<User>(
`SELECT * FROM users WHERE id = $1`
);
return user;
}
}
// Manual wiring (composition root)
function createServices() {
const logger = new ConsoleLogger();
const database = new PostgresDB();
const userService = new UserService(database, logger);
return {
logger,
database,
userService
};
}
// Usage
const services = createServices();
const user = await services.userService.getUser('123');
// Testing - inject mocks
const mockDB: Database = {
query: jest.fn().mockResolvedValue([{ id: '123', name: 'Alice' }])
};
const mockLogger: Logger = { log: jest.fn() };
const testService = new UserService(mockDB, mockLogger);
// Type-safe factory pattern
type ServiceFactory<T> = () => T;
class ServiceContainer {
private factories = new Map<string, ServiceFactory<any>>();
private instances = new Map<string, any>();
register<T>(name: string, factory: ServiceFactory<T>): void {
this.factories.set(name, factory);
}
get<T>(name: string): T {
if (!this.instances.has(name)) {
const factory = this.factories.get(name);
if (!factory) throw new Error(`Service ${name} not registered`);
this.instances.set(name, factory());
}
return this.instances.get(name) as T;
}
}
// Usage
const container = new ServiceContainer();
container.register<Logger>('logger', () => new ConsoleLogger());
container.register<Database>('database', () => new PostgresDB());
container.register<UserService>(
'userService',
() => new UserService(
container.get<Database>('database'),
container.get<Logger>('logger')
)
);
const service = container.get<UserService>('userService');
2. Plugin Architecture and Type-safe APIs
| Pattern | Implementation | Type Safety | Benefit |
|---|---|---|---|
| Plugin Interface | Define contract for all plugins | Enforce plugin structure | Consistent plugin API |
| Hook System | Type-safe event hooks with generics | Typed hook parameters and returns | Extensibility without modification |
| Plugin Registry | Type-safe plugin registration and discovery | Infer plugin types automatically | Dynamic plugin loading |
| Middleware Pattern | Composable middleware chain | Type-safe context passing | Request/response transformation |
| Extension Points | Predefined places for extension | Typed extension contracts | Open-closed principle |
| Builder Pattern | Fluent API with type constraints | Type-safe method chaining | Progressive configuration |
Example: Type-safe plugin system
// Define plugin interface
interface Plugin<TConfig = unknown> {
name: string;
version: string;
config?: TConfig;
// Lifecycle hooks
init?(context: PluginContext): void | Promise<void>;
beforeRequest?(req: Request): Request | Promise<Request>;
afterResponse?(res: Response): Response | Promise<Response>;
destroy?(): void | Promise<void>;
}
// Plugin context with typed services
interface PluginContext {
logger: Logger;
config: AppConfig;
registerHook<T>(name: string, handler: HookHandler<T>): void;
}
// Hook system with generics
type HookHandler<T> = (data: T) => T | Promise<T>;
class HookManager {
private hooks = new Map<string, HookHandler<any>[]>();
register<T>(name: string, handler: HookHandler<T>): void {
const handlers = this.hooks.get(name) ?? [];
handlers.push(handler);
this.hooks.set(name, handlers);
}
async execute<T>(name: string, data: T): Promise<T> {
const handlers = this.hooks.get(name) ?? [];
let result = data;
for (const handler of handlers) {
result = await handler(result);
}
return result;
}
}
// Plugin registry with type inference
class PluginRegistry {
private plugins = new Map<string, Plugin>();
register<T extends Plugin>(plugin: T): void {
this.plugins.set(plugin.name, plugin);
}
get<T extends Plugin>(name: string): T | undefined {
return this.plugins.get(name) as T | undefined;
}
getAll(): Plugin[] {
return Array.from(this.plugins.values());
}
}
// Example plugins
interface LoggingPluginConfig {
level: 'info' | 'debug' | 'error';
format: 'json' | 'text';
}
class LoggingPlugin implements Plugin<LoggingPluginConfig> {
name = 'logging';
version = '1.0.0';
constructor(public config: LoggingPluginConfig) {}
init(context: PluginContext): void {
context.logger.log(`Logging plugin initialized with level: ${this.config.level}`);
}
async beforeRequest(req: Request): Promise<Request> {
console.log(`[${this.config.level}] Request: ${req.url}`);
return req;
}
}
interface CachePluginConfig {
ttl: number;
maxSize: number;
}
class CachePlugin implements Plugin<CachePluginConfig> {
name = 'cache';
version = '1.0.0';
private cache = new Map<string, any>();
constructor(public config: CachePluginConfig) {}
async afterResponse(res: Response): Promise<Response> {
// Cache logic
return res;
}
}
// Application with plugins
class Application {
private registry = new PluginRegistry();
private hooks = new HookManager();
use<T extends Plugin>(plugin: T): this {
this.registry.register(plugin);
const context: PluginContext = {
logger: console,
config: {},
registerHook: (name, handler) => this.hooks.register(name, handler)
};
plugin.init?.(context);
return this; // Fluent API
}
async handleRequest(req: Request): Promise<Response> {
// Execute beforeRequest hooks
const modifiedReq = await this.hooks.execute('beforeRequest', req);
// Process request
const response = await this.processRequest(modifiedReq);
// Execute afterResponse hooks
return this.hooks.execute('afterResponse', response);
}
private async processRequest(req: Request): Promise<Response> {
// Implementation
return new Response();
}
}
// Usage - fully typed!
const app = new Application()
.use(new LoggingPlugin({ level: 'debug', format: 'json' }))
.use(new CachePlugin({ ttl: 3600, maxSize: 100 }));
await app.handleRequest(new Request('https://api.example.com'));
3. Schema Validation Integration (Zod, Joi)
| Library | Features | Type Inference | Use Case |
|---|---|---|---|
| Zod | TypeScript-first, composable, no dependencies | Infer types from schemas - z.infer<typeof schema> | API validation, form validation, config parsing |
| Joi | Mature, extensive validators, plugins | Manual type definitions or joi-to-typescript | Node.js APIs, configuration validation |
| Yup | Similar to Joi, smaller bundle | InferType<typeof schema> utility | Form validation (React Hook Form, Formik) |
| io-ts | Functional programming style, codec pattern | Runtime + compile-time validation | Functional codebases, API boundaries |
| class-validator | Decorator-based validation | Works with TypeScript classes | NestJS, class-based DTOs |
| ArkType | Ultra-fast, TypeScript syntax | 1:1 TypeScript to runtime validation | Performance-critical validation |
Example: Zod schema validation
// Install: npm i zod
import { z } from 'zod';
// Define schema
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().positive().optional(),
role: z.enum(['admin', 'user', 'guest']),
createdAt: z.date(),
metadata: z.record(z.unknown()).optional()
});
// Infer TypeScript type from schema
type User = z.infer<typeof UserSchema>;
// Equivalent to:
// type User = {
// id: string;
// name: string;
// email: string;
// age?: number | undefined;
// role: 'admin' | 'user' | 'guest';
// createdAt: Date;
// metadata?: Record<string, unknown> | undefined;
// }
// Validation
function createUser(data: unknown): User {
// Parse and validate - throws on error
return UserSchema.parse(data);
}
// Safe parsing - returns result object
function safeCreateUser(data: unknown): User | null {
const result = UserSchema.safeParse(data);
if (result.success) {
return result.data; // Typed as User
} else {
console.error(result.error.issues);
return null;
}
}
// Nested schemas
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string().regex(/^\d{5}$/),
country: z.string().length(2) // ISO country code
});
const UserWithAddressSchema = UserSchema.extend({
address: AddressSchema,
billingAddress: AddressSchema.optional()
});
type UserWithAddress = z.infer<typeof UserWithAddressSchema>;
// Array and union schemas
const UsersSchema = z.array(UserSchema);
type Users = z.infer<typeof UsersSchema>; // User[]
const StringOrNumberSchema = z.union([z.string(), z.number()]);
type StringOrNumber = z.infer<typeof StringOrNumberSchema>; // string | number
// Refinements and custom validation
const PasswordSchema = z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[0-9]/, 'Password must contain number');
const ConfirmPasswordSchema = z.object({
password: PasswordSchema,
confirmPassword: z.string()
}).refine(
(data) => data.password === data.confirmPassword,
{ message: 'Passwords must match', path: ['confirmPassword'] }
);
// Transform data
const DateStringSchema = z.string().transform((str) => new Date(str));
const trimmedString = z.string().transform((s) => s.trim());
// API endpoint with validation
import { Request, Response } from 'express';
const CreateUserRequestSchema = z.object({
body: UserSchema.omit({ id: true, createdAt: true }),
query: z.object({
sendEmail: z.string().transform(s => s === 'true').optional()
})
});
async function createUserHandler(req: Request, res: Response) {
try {
const { body, query } = CreateUserRequestSchema.parse({
body: req.body,
query: req.query
});
// body and query are fully typed!
const user = await saveUser(body);
if (query.sendEmail) {
await sendWelcomeEmail(user.email);
}
res.json(user);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ errors: error.issues });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
}
Example: Joi validation
// Install: npm i joi @types/joi
import Joi from 'joi';
// Define schema
const userSchema = Joi.object({
id: Joi.string().uuid().required(),
name: Joi.string().min(1).max(100).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().positive().optional(),
role: Joi.string().valid('admin', 'user', 'guest').required(),
createdAt: Joi.date().required(),
metadata: Joi.object().unknown(true).optional()
});
// Manual type definition (or use joi-to-typescript)
interface User {
id: string;
name: string;
email: string;
age?: number;
role: 'admin' | 'user' | 'guest';
createdAt: Date;
metadata?: Record<string, unknown>;
}
// Validation
function validateUser(data: unknown): User {
const { error, value } = userSchema.validate(data, {
abortEarly: false, // Return all errors
stripUnknown: true // Remove unknown properties
});
if (error) {
throw new Error(`Validation failed: ${error.message}`);
}
return value as User;
}
// Express middleware for validation
function validate(schema: Joi.Schema) {
return (req: Request, res: Response, next: Function) => {
const { error, value } = schema.validate(req.body, {
abortEarly: false
});
if (error) {
const errors = error.details.map(d => ({
field: d.path.join('.'),
message: d.message
}));
return res.status(400).json({ errors });
}
req.body = value; // Replace with validated data
next();
};
}
// Usage
app.post('/users', validate(userSchema), createUserHandler);
4. ORM Integration (Prisma, TypeORM) Type Safety
| ORM | Type Generation | Query Builder | Features |
|---|---|---|---|
| Prisma | Generate types from schema - prisma generate | Fluent API, full type inference | Best-in-class types, migrations, Prisma Studio |
| TypeORM | Decorators on entities - infer from classes | Repository pattern, QueryBuilder | Mature, Active Record/Data Mapper patterns |
| Drizzle ORM | TypeScript-first, schema definition | SQL-like builder with full types | Lightweight, edge-compatible, serverless |
| Kysely | Type-safe SQL query builder | Composable queries with inference | Raw SQL control with type safety |
| MikroORM | Entity classes with decorators | Unit of Work, Identity Map | DDD patterns, complex relationships |
Example: Prisma type-safe ORM
// Install: npm i @prisma/client
// Dev: npm i -D prisma
// Initialize Prisma
// npx prisma init
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(uuid())
email String @unique
name String
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(uuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
tags Tag[]
createdAt DateTime @default(now())
}
model Profile {
id String @id @default(uuid())
bio String
userId String @unique
user User @relation(fields: [userId], references: [id])
}
model Tag {
id String @id @default(uuid())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
MODERATOR
}
// Generate Prisma Client
// npx prisma generate
// Usage - fully typed!
import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();
// Type-safe queries
async function getUser(id: string) {
const user = await prisma.user.findUnique({
where: { id },
include: {
posts: true,
profile: true
}
});
// user type is inferred: User & { posts: Post[], profile: Profile | null }
return user;
}
// Type-safe create
async function createUser(data: Prisma.UserCreateInput) {
return prisma.user.create({
data: {
email: data.email,
name: data.name,
profile: {
create: {
bio: 'New user'
}
}
}
});
}
// Type-safe where conditions
async function findUsers(filter: Prisma.UserWhereInput) {
return prisma.user.findMany({
where: filter,
orderBy: { createdAt: 'desc' }
});
}
// Usage
const admins = await findUsers({ role: 'ADMIN' }); // Fully typed!
// Type-safe updates
await prisma.user.update({
where: { id: '123' },
data: {
name: 'New Name',
posts: {
create: {
title: 'New Post',
content: 'Content here'
}
}
}
});
// Aggregations with types
const stats = await prisma.post.aggregate({
_count: { id: true },
_avg: { authorId: false },
where: { published: true }
});
// stats is fully typed
// Transactions
await prisma.$transaction([
prisma.user.create({ data: { email: 'user@example.com', name: 'User' } }),
prisma.post.create({ data: { title: 'Post', authorId: '...' } })
]);
// Raw queries with types
const users = await prisma.$queryRaw<User[]>`
SELECT * FROM "User" WHERE role = ${Prisma.Role.ADMIN}
`;
Example: TypeORM entities
// Install: npm i typeorm reflect-metadata
// Database driver: npm i pg (for PostgreSQL)
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
// entities/User.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
OneToOne
} from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Column({ unique: true })
email!: string;
@Column()
name!: string;
@Column({ type: 'enum', enum: ['USER', 'ADMIN', 'MODERATOR'], default: 'USER' })
role!: 'USER' | 'ADMIN' | 'MODERATOR';
@OneToMany(() => Post, post => post.author)
posts!: Post[];
@OneToOne(() => Profile, profile => profile.user)
profile?: Profile;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
}
// entities/Post.ts
@Entity()
export class Post {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Column()
title!: string;
@Column({ type: 'text', nullable: true })
content?: string;
@Column({ default: false })
published!: boolean;
@ManyToOne(() => User, user => user.posts)
author!: User;
@CreateDateColumn()
createdAt!: Date;
}
// data-source.ts
import { DataSource } from 'typeorm';
export const AppDataSource = new DataSource({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'user',
password: 'password',
database: 'mydb',
entities: [User, Post, Profile],
synchronize: true, // Don't use in production!
logging: true
});
// Usage
await AppDataSource.initialize();
const userRepository = AppDataSource.getRepository(User);
// Type-safe queries
const user = await userRepository.findOne({
where: { email: 'user@example.com' },
relations: ['posts', 'profile']
});
// QueryBuilder with types
const users = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.role = :role', { role: 'ADMIN' })
.andWhere('post.published = :published', { published: true })
.getMany();
// Type-safe create
const newUser = userRepository.create({
email: 'new@example.com',
name: 'New User',
role: 'USER'
});
await userRepository.save(newUser);
// Transactions
await AppDataSource.transaction(async (manager) => {
const user = manager.create(User, { email: 'user@example.com', name: 'User' });
await manager.save(user);
const post = manager.create(Post, { title: 'Post', author: user });
await manager.save(post);
});
5. GraphQL Code Generation and Type Safety
| Tool | Purpose | Output | Use Case |
|---|---|---|---|
| GraphQL Code Generator | Generate TypeScript from GraphQL schema/operations | Types, hooks, resolvers, operations | Full-stack GraphQL apps |
| Apollo Client | GraphQL client with TypeScript support | useQuery, useMutation hooks with types | React/Vue GraphQL frontends |
| TypeGraphQL | Create GraphQL schema using TypeScript classes | Schema-first from decorators | TypeScript-first GraphQL APIs |
| Pothos GraphQL | Code-first GraphQL schema builder | Type-safe schema construction | Type-safe server schema definition |
| gql-tag | Parse GraphQL queries | Tagged template literals | Client-side query definition |
| GraphQL ESLint | Lint GraphQL operations | Catch errors at compile time | Query validation |
Example: GraphQL Code Generator
// Install: npm i -D @graphql-codegen/cli @graphql-codegen/typescript
// npm i -D @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
// schema.graphql
type User {
id: ID!
email: String!
name: String!
role: Role!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String
published: Boolean!
author: User!
}
enum Role {
USER
ADMIN
MODERATOR
}
type Query {
user(id: ID!): User
users(role: Role): [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
input CreateUserInput {
email: String!
name: String!
role: Role = USER
}
input UpdateUserInput {
email: String
name: String
role: Role
}
// codegen.yml
overwrite: true
schema: "http://localhost:4000/graphql"
documents: "src/**/*.graphql"
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-operations"
- "typescript-react-apollo"
config:
withHooks: true
withComponent: false
// queries.graphql
query GetUser($id: ID!) {
user(id: $id) {
id
email
name
role
posts {
id
title
published
}
}
}
query GetUsers($role: Role) {
users(role: $role) {
id
email
name
role
}
}
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
email
name
role
}
}
// Generate types
// npx graphql-codegen
// Usage - generated hooks are fully typed!
import { useGetUserQuery, useCreateUserMutation } from './generated/graphql';
function UserProfile({ userId }: { userId: string }) {
const { data, loading, error } = useGetUserQuery({
variables: { id: userId }
});
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
// data.user is fully typed!
return (
<div>
<h1>{data?.user?.name}</h1>
<p>{data?.user?.email}</p>
<ul>
{data?.user?.posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
function CreateUserForm() {
const [createUser, { loading, error }] = useCreateUserMutation();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await createUser({
variables: {
input: {
email: 'new@example.com',
name: 'New User',
role: 'USER' // Typed as Role enum!
}
}
});
};
return <form onSubmit={handleSubmit}>...</form>;
}
6. Microservice Communication and API Contracts
| Pattern/Tool | Implementation | Type Safety | Use Case |
|---|---|---|---|
| tRPC | End-to-end type-safe APIs without code generation | Infer client types from server | TypeScript monorepos, full-stack apps |
| OpenAPI/Swagger | API specification with TypeScript generation | Generate types from OpenAPI spec | REST APIs, polyglot services |
| gRPC | Protocol Buffers with TypeScript | Generate from .proto files | High-performance microservices |
| Message Queue | Type-safe message schemas (Zod/JSON Schema) | Validate messages at runtime | Event-driven architecture |
| API Client Generation | Generate SDK from API definition | Auto-generated type-safe client | External API integration |
| Contract Testing | Pact, MSW for API contracts | Type-safe mock servers | Consumer-driven contracts |
Example: tRPC end-to-end type safety
// Install: npm i @trpc/server @trpc/client @trpc/react-query
// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
// Define router
export const appRouter = t.router({
// Query
getUser: t.procedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
// Fetch user from database
return {
id: input.id,
name: 'Alice',
email: 'alice@example.com'
};
}),
// Query with transformed output
getUsers: t.procedure
.input(z.object({
role: z.enum(['admin', 'user']).optional()
}))
.query(async ({ input }) => {
// Return array of users
return [];
}),
// Mutation
createUser: t.procedure
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
role: z.enum(['admin', 'user']).default('user')
}))
.mutation(async ({ input }) => {
// Create user in database
return {
id: '123',
...input
};
}),
// Nested routers
posts: t.router({
list: t.procedure.query(() => []),
get: t.procedure
.input(z.object({ id: z.string() }))
.query(({ input }) => null)
})
});
export type AppRouter = typeof appRouter;
// server/index.ts
import { createHTTPServer } from '@trpc/server/adapters/standalone';
createHTTPServer({
router: appRouter,
createContext: () => ({})
}).listen(3000);
// client/trpc.ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000'
})
]
});
// Usage - fully typed!
const user = await trpc.getUser.query({ id: '123' });
// user is typed based on server return type!
const newUser = await trpc.createUser.mutate({
name: 'Bob',
email: 'bob@example.com',
role: 'admin'
});
// Input is validated with Zod, output is typed!
// React integration
import { createTRPCReact } from '@trpc/react-query';
export const trpcReact = createTRPCReact<AppRouter>();
// In component
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading } = trpcReact.getUser.useQuery({ id: userId });
// data is fully typed!
return <div>{data?.name}</div>;
}
// Mutation
function CreateUser() {
const mutation = trpcReact.createUser.useMutation();
const handleSubmit = () => {
mutation.mutate({
name: 'Charlie',
email: 'charlie@example.com'
});
};
return <button onClick={handleSubmit}>Create</button>;
}
Example: OpenAPI type generation
// openapi.yaml
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users/{id}:
get:
operationId: getUser
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: User found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
/users:
post:
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required:
- id
- email
- name
properties:
id:
type: string
email:
type: string
format: email
name:
type: string
role:
type: string
enum: [user, admin]
CreateUserRequest:
type: object
required:
- email
- name
properties:
email:
type: string
format: email
name:
type: string
role:
type: string
enum: [user, admin]
default: user
// Install: npm i -D openapi-typescript
// Generate types: npx openapi-typescript openapi.yaml -o src/api.d.ts
// api.d.ts (generated)
export interface paths {
'/users/{id}': {
get: operations['getUser'];
};
'/users': {
post: operations['createUser'];
};
}
export interface components {
schemas: {
User: {
id: string;
email: string;
name: string;
role?: 'user' | 'admin';
};
CreateUserRequest: {
email: string;
name: string;
role?: 'user' | 'admin';
};
};
}
export interface operations {
getUser: {
parameters: { path: { id: string } };
responses: { 200: { content: { 'application/json': components['schemas']['User'] } } };
};
createUser: {
requestBody: { content: { 'application/json': components['schemas']['CreateUserRequest'] } };
responses: { 201: { content: { 'application/json': components['schemas']['User'] } } };
};
}
// Usage with type-safe client
import type { paths } from './api';
import createClient from 'openapi-fetch';
const client = createClient<paths>({ baseUrl: 'https://api.example.com' });
// Fully typed requests and responses!
const { data, error } = await client.GET('/users/{id}', {
params: { path: { id: '123' } }
});
// data is typed as User
const { data: newUser } = await client.POST('/users', {
body: {
email: 'user@example.com',
name: 'User',
role: 'admin'
}
});
// body and data are fully typed!
Note: Enterprise patterns best practices:
- Dependency Injection - Use InversifyJS or TSyringe for complex apps, manual DI for simplicity
- Plugins - Define plugin interfaces, type-safe hook system, plugin registry with generics
- Validation - Use Zod for TypeScript-first validation, infer types from schemas
- ORMs - Prisma offers best TypeScript experience, TypeORM for mature ecosystem
- GraphQL - Use GraphQL Code Generator for full type safety, tRPC for TypeScript-only stacks
- Microservices - tRPC for monorepos, OpenAPI for polyglot, gRPC for performance
Advanced Patterns and Enterprise Features Summary
- Dependency Injection - InversifyJS/TSyringe for complex DI, manual injection for simple cases
- Plugin Architecture - Type-safe plugin interfaces, hook systems, registries with generics
- Schema Validation - Zod for TypeScript-first validation with type inference from schemas
- ORMs - Prisma (best types), TypeORM (mature), Drizzle (lightweight), all with full type safety
- GraphQL - Code generation from schema/queries, TypeGraphQL for code-first, full type safety
- Microservices - tRPC for end-to-end types, OpenAPI generation, gRPC with Protocol Buffers