Claude Agent Skill · by Bobmatnyc

Nodejs Backend Typescript

Install Nodejs Backend Typescript skill for Claude Code from bobmatnyc/claude-mpm-skills.

Install
Terminal · npx
$npx skills add https://github.com/obra/superpowers --skill brainstorming
Works with Paperclip

How Nodejs Backend Typescript fits into a Paperclip company.

Nodejs Backend Typescript drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md1030 lines
Expand
---name: nodejs-backend-typescriptdescription: "Node.js backend development with TypeScript, Express/Fastify servers, routing, middleware, and database integration"user-invocable: falsedisable-model-invocation: trueprogressive_disclosure:  entry_point:    summary: "Node.js backend development with TypeScript, Express/Fastify servers, routing, middleware, and database integration"    when_to_use: "When working with nodejs-backend-typescript or related functionality."    quick_start: "1. Review the core concepts below. 2. Apply patterns to your use case. 3. Follow best practices for implementation."---# Node.js Backend Development with TypeScript ---progressive_disclosure:  entry_point:    summary: "TypeScript backend patterns with Express/Fastify, routing, middleware, database integration"    when_to_use:      - "When building REST APIs with TypeScript"      - "When creating Express/Fastify servers"      - "When needing server-side TypeScript"      - "When building microservices"    quick_start:      - "npm init -y && npm install -D typescript @types/node tsx"      - "npm install express @types/express zod"      - "Create tsconfig.json with strict mode"      - "npm run dev"  token_estimate:    entry: 75    full: 4700--- ## TypeScript Setup ### Essential Configuration **tsconfig.json** (strict mode recommended):```json{  "compilerOptions": {    "target": "ES2022",    "module": "NodeNext",    "moduleResolution": "NodeNext",    "outDir": "./dist",    "rootDir": "./src",    "strict": true,    "esModuleInterop": true,    "skipLibCheck": true,    "forceConsistentCasingInFileNames": true,    "resolveJsonModule": true,    "types": ["node"]  },  "include": ["src/**/*"],  "exclude": ["node_modules", "dist"]}``` **package.json scripts**:```json{  "scripts": {    "dev": "tsx watch src/server.ts",    "build": "tsc",    "start": "node dist/server.js",    "test": "vitest"  }}``` ### Development Dependencies```bashnpm install -D typescript @types/node tsx vitestnpm install -D @types/express  # or @types/node (Fastify has built-in types)``` ## Express Patterns ### Basic Express Server **src/server.ts**:```typescriptimport express, { Request, Response, NextFunction } from 'express';import { z } from 'zod'; const app = express();const port = process.env.PORT || 3000; // Middlewareapp.use(express.json());app.use(express.urlencoded({ extended: true })); // Type-safe request handlersinterface TypedRequest<T> extends Request {  body: T;} // Routesapp.get('/health', (req: Request, res: Response) => {  res.json({ status: 'ok', timestamp: new Date().toISOString() });}); // Start serverapp.listen(port, () => {  console.log(`Server running on port ${port}`);});``` ### Router Pattern **src/routes/users.ts**:```typescriptimport { Router } from 'express';import { z } from 'zod';import { validateRequest } from '../middleware/validation'; const router = Router(); const createUserSchema = z.object({  email: z.string().email(),  name: z.string().min(2),  age: z.number().int().positive().optional(),}); router.post(  '/users',  validateRequest(createUserSchema),  async (req, res, next) => {    try {      const userData = req.body; // Type-safe after validation      // Database insert logic      res.status(201).json({ id: 1, ...userData });    } catch (error) {      next(error);    }  }); export default router;``` ### Middleware Patterns **src/middleware/validation.ts**:```typescriptimport { Request, Response, NextFunction } from 'express';import { z, ZodSchema } from 'zod'; export const validateRequest = (schema: ZodSchema) => {  return (req: Request, res: Response, next: NextFunction) => {    try {      req.body = schema.parse(req.body);      next();    } catch (error) {      if (error instanceof z.ZodError) {        res.status(400).json({          error: 'Validation failed',          details: error.errors,        });      } else {        next(error);      }    }  };};``` **src/middleware/auth.ts**:```typescriptimport { Request, Response, NextFunction } from 'express';import jwt from 'jsonwebtoken'; interface JwtPayload {  userId: string;  email: string;} declare global {  namespace Express {    interface Request {      user?: JwtPayload;    }  }} export const authenticate = (  req: Request,  res: Response,  next: NextFunction) => {  const token = req.headers.authorization?.replace('Bearer ', '');   if (!token) {    return res.status(401).json({ error: 'No token provided' });  }   try {    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;    req.user = decoded;    next();  } catch (error) {    res.status(401).json({ error: 'Invalid token' });  }};``` ### Error Handling **src/middleware/errorHandler.ts**:```typescriptimport { Request, Response, NextFunction } from 'express'; export class AppError extends Error {  constructor(    public statusCode: number,    message: string,    public isOperational = true  ) {    super(message);    Object.setPrototypeOf(this, AppError.prototype);  }} export const errorHandler = (  err: Error,  req: Request,  res: Response,  next: NextFunction) => {  if (err instanceof AppError) {    return res.status(err.statusCode).json({      error: err.message,      ...(process.env.NODE_ENV === 'development' && { stack: err.stack }),    });  }   console.error('Unexpected error:', err);  res.status(500).json({    error: 'Internal server error',    ...(process.env.NODE_ENV === 'development' && {      message: err.message,      stack: err.stack,    }),  });};``` ## Fastify Patterns ### Basic Fastify Server **src/server.ts**:```typescriptimport Fastify from 'fastify';import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';import { Type } from '@sinclair/typebox'; const fastify = Fastify({  logger: {    level: process.env.LOG_LEVEL || 'info',  },}).withTypeProvider<TypeBoxTypeProvider>(); // Type-safe route with schema validationfastify.route({  method: 'POST',  url: '/users',  schema: {    body: Type.Object({      email: Type.String({ format: 'email' }),      name: Type.String({ minLength: 2 }),      age: Type.Optional(Type.Integer({ minimum: 0 })),    }),    response: {      201: Type.Object({        id: Type.Number(),        email: Type.String(),        name: Type.String(),      }),    },  },  handler: async (request, reply) => {    const { email, name, age } = request.body;    // Auto-typed and validated    return reply.status(201).send({ id: 1, email, name });  },}); const start = async () => {  try {    await fastify.listen({ port: 3000, host: '0.0.0.0' });  } catch (err) {    fastify.log.error(err);    process.exit(1);  }}; start();``` ### Plugin Pattern **src/plugins/database.ts**:```typescriptimport { FastifyPluginAsync } from 'fastify';import fp from 'fastify-plugin';import { drizzle } from 'drizzle-orm/node-postgres';import { Pool } from 'pg'; declare module 'fastify' {  interface FastifyInstance {    db: ReturnType<typeof drizzle>;  }} const databasePlugin: FastifyPluginAsync = async (fastify) => {  const pool = new Pool({    connectionString: process.env.DATABASE_URL,  });   const db = drizzle(pool);  fastify.decorate('db', db);   fastify.addHook('onClose', async () => {    await pool.end();  });}; export default fp(databasePlugin);``` ### Hooks Pattern **src/hooks/auth.ts**:```typescriptimport { FastifyRequest, FastifyReply } from 'fastify';import jwt from 'jsonwebtoken'; declare module 'fastify' {  interface FastifyRequest {    user?: {      userId: string;      email: string;    };  }} export const authHook = async (  request: FastifyRequest,  reply: FastifyReply) => {  const token = request.headers.authorization?.replace('Bearer ', '');   if (!token) {    return reply.status(401).send({ error: 'No token provided' });  }   try {    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as {      userId: string;      email: string;    };    request.user = decoded;  } catch (error) {    return reply.status(401).send({ error: 'Invalid token' });  }};``` ## Request Validation ### Zod with Express ```typescriptimport { z } from 'zod'; const userSchema = z.object({  email: z.string().email(),  password: z.string().min(8),  profile: z.object({    firstName: z.string(),    lastName: z.string(),    age: z.number().int().positive(),  }),  tags: z.array(z.string()).optional(),}); type CreateUserInput = z.infer<typeof userSchema>; router.post('/users', async (req, res) => {  const result = userSchema.safeParse(req.body);   if (!result.success) {    return res.status(400).json({      error: 'Validation failed',      details: result.error.format(),    });  }   const user: CreateUserInput = result.data;  // Type-safe user object});``` ### TypeBox with Fastify ```typescriptimport { Type, Static } from '@sinclair/typebox'; const UserSchema = Type.Object({  email: Type.String({ format: 'email' }),  password: Type.String({ minLength: 8 }),  profile: Type.Object({    firstName: Type.String(),    lastName: Type.String(),    age: Type.Integer({ minimum: 0 }),  }),  tags: Type.Optional(Type.Array(Type.String())),}); type User = Static<typeof UserSchema>; fastify.post('/users', {  schema: { body: UserSchema },  handler: async (request, reply) => {    const user: User = request.body; // Auto-validated    return { id: 1, ...user };  },});``` ## Authentication ### JWT Authentication **src/services/auth.ts**:```typescriptimport jwt from 'jsonwebtoken';import bcrypt from 'bcrypt'; interface TokenPayload {  userId: string;  email: string;} export class AuthService {  private static JWT_SECRET = process.env.JWT_SECRET!;  private static JWT_EXPIRES_IN = '7d';   static async hashPassword(password: string): Promise<string> {    return bcrypt.hash(password, 10);  }   static async comparePassword(    password: string,    hash: string  ): Promise<boolean> {    return bcrypt.compare(password, hash);  }   static generateToken(payload: TokenPayload): string {    return jwt.sign(payload, this.JWT_SECRET, {      expiresIn: this.JWT_EXPIRES_IN,    });  }   static verifyToken(token: string): TokenPayload {    return jwt.verify(token, this.JWT_SECRET) as TokenPayload;  }}``` ### Session-based Auth (Express) ```typescriptimport session from 'express-session';import RedisStore from 'connect-redis';import { createClient } from 'redis'; const redisClient = createClient({  url: process.env.REDIS_URL,});redisClient.connect(); app.use(  session({    store: new RedisStore({ client: redisClient }),    secret: process.env.SESSION_SECRET!,    resave: false,    saveUninitialized: false,    cookie: {      secure: process.env.NODE_ENV === 'production',      httpOnly: true,      maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days    },  })); declare module 'express-session' {  interface SessionData {    userId: string;  }}``` ## Database Integration ### Drizzle ORM **src/db/schema.ts**:```typescriptimport { pgTable, serial, varchar, timestamp } from 'drizzle-orm/pg-core'; export const users = pgTable('users', {  id: serial('id').primaryKey(),  email: varchar('email', { length: 255 }).notNull().unique(),  name: varchar('name', { length: 255 }).notNull(),  passwordHash: varchar('password_hash', { length: 255 }).notNull(),  createdAt: timestamp('created_at').defaultNow().notNull(),}); export type User = typeof users.$inferSelect;export type NewUser = typeof users.$inferInsert;``` **src/db/client.ts**:```typescriptimport { drizzle } from 'drizzle-orm/node-postgres';import { Pool } from 'pg';import * as schema from './schema'; const pool = new Pool({  connectionString: process.env.DATABASE_URL,}); export const db = drizzle(pool, { schema });``` **src/repositories/userRepository.ts**:```typescriptimport { eq } from 'drizzle-orm';import { db } from '../db/client';import { users, NewUser } from '../db/schema'; export class UserRepository {  static async create(data: NewUser) {    const [user] = await db.insert(users).values(data).returning();    return user;  }   static async findByEmail(email: string) {    return db.query.users.findFirst({      where: eq(users.email, email),    });  }   static async findById(id: number) {    return db.query.users.findFirst({      where: eq(users.id, id),    });  }   static async list(limit = 10, offset = 0) {    return db.query.users.findMany({      limit,      offset,      columns: {        passwordHash: false, // Exclude sensitive fields      },    });  }}``` ### Prisma **prisma/schema.prisma**:```prismadatasource db {  provider = "postgresql"  url      = env("DATABASE_URL")} generator client {  provider = "prisma-client-js"} model User {  id           Int      @id @default(autoincrement())  email        String   @unique  name         String  passwordHash String   @map("password_hash")  createdAt    DateTime @default(now()) @map("created_at")  posts        Post[]   @@map("users")} model Post {  id        Int      @id @default(autoincrement())  title     String  content   String?  published Boolean  @default(false)  authorId  Int      @map("author_id")  author    User     @relation(fields: [authorId], references: [id])  createdAt DateTime @default(now()) @map("created_at")   @@map("posts")}``` **src/services/userService.ts**:```typescriptimport { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); export class UserService {  static async createUser(data: { email: string; name: string; password: string }) {    const passwordHash = await AuthService.hashPassword(data.password);     return prisma.user.create({      data: {        email: data.email,        name: data.name,        passwordHash,      },      select: {        id: true,        email: true,        name: true,        createdAt: true,      },    });  }   static async getUserWithPosts(userId: number) {    return prisma.user.findUnique({      where: { id: userId },      include: {        posts: {          where: { published: true },          orderBy: { createdAt: 'desc' },        },      },    });  }}``` ## API Design ### REST API Patterns **Pagination**:```typescriptimport { z } from 'zod'; const paginationSchema = z.object({  page: z.coerce.number().int().positive().default(1),  limit: z.coerce.number().int().positive().max(100).default(20),}); router.get('/users', async (req, res) => {  const { page, limit } = paginationSchema.parse(req.query);  const offset = (page - 1) * limit;   const [users, total] = await Promise.all([    UserRepository.list(limit, offset),    UserRepository.count(),  ]);   res.json({    data: users,    pagination: {      page,      limit,      total,      totalPages: Math.ceil(total / limit),    },  });});``` **Filtering and Sorting**:```typescriptconst filterSchema = z.object({  status: z.enum(['active', 'inactive']).optional(),  search: z.string().optional(),  sortBy: z.enum(['createdAt', 'name', 'email']).default('createdAt'),  sortOrder: z.enum(['asc', 'desc']).default('desc'),}); router.get('/users', async (req, res) => {  const filters = filterSchema.parse(req.query);   const users = await db.query.users.findMany({    where: and(      filters.status && eq(users.status, filters.status),      filters.search && ilike(users.name, `%${filters.search}%`)    ),    orderBy: [      filters.sortOrder === 'asc'        ? asc(users[filters.sortBy])        : desc(users[filters.sortBy]),    ],  });   res.json({ data: users });});``` ### Error Response Format ```typescriptinterface ErrorResponse {  error: string;  message: string;  statusCode: number;  details?: unknown;  timestamp: string;  path: string;} export const formatError = (  err: AppError,  req: Request): ErrorResponse => ({  error: err.name,  message: err.message,  statusCode: err.statusCode,  ...(err.details && { details: err.details }),  timestamp: new Date().toISOString(),  path: req.path,});``` ## Environment Configuration ### Type-safe Environment Variables **src/config/env.ts**:```typescriptimport { z } from 'zod'; const envSchema = z.object({  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),  PORT: z.coerce.number().default(3000),  DATABASE_URL: z.string().url(),  REDIS_URL: z.string().url(),  JWT_SECRET: z.string().min(32),  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),}); export type Env = z.infer<typeof envSchema>; export const env = envSchema.parse(process.env);``` **Usage**:```typescriptimport { env } from './config/env'; const port = env.PORT; // Type-safe, validated``` ## Testing ### Vitest Setup **vitest.config.ts**:```typescriptimport { defineConfig } from 'vitest/config'; export default defineConfig({  test: {    globals: true,    environment: 'node',    setupFiles: ['./src/tests/setup.ts'],    coverage: {      provider: 'v8',      reporter: ['text', 'json', 'html'],    },  },});``` ### Integration Tests with Supertest **src/tests/users.test.ts**:```typescriptimport { describe, it, expect, beforeAll, afterAll } from 'vitest';import request from 'supertest';import { app } from '../server';import { db } from '../db/client'; describe('User API', () => {  beforeAll(async () => {    // Setup test database    await db.delete(users);  });   afterAll(async () => {    // Cleanup  });   it('should create a new user', async () => {    const response = await request(app)      .post('/users')      .send({        email: 'test@example.com',        name: 'Test User',        password: 'password123',      })      .expect(201);     expect(response.body).toMatchObject({      email: 'test@example.com',      name: 'Test User',    });    expect(response.body).toHaveProperty('id');    expect(response.body).not.toHaveProperty('passwordHash');  });   it('should return 400 for invalid email', async () => {    const response = await request(app)      .post('/users')      .send({        email: 'invalid-email',        name: 'Test User',        password: 'password123',      })      .expect(400);     expect(response.body).toHaveProperty('error');  });});``` ### Unit Tests **src/services/auth.test.ts**:```typescriptimport { describe, it, expect } from 'vitest';import { AuthService } from './auth'; describe('AuthService', () => {  it('should hash password correctly', async () => {    const password = 'mySecurePassword123';    const hash = await AuthService.hashPassword(password);     expect(hash).not.toBe(password);    expect(hash.length).toBeGreaterThan(50);  });   it('should verify password correctly', async () => {    const password = 'mySecurePassword123';    const hash = await AuthService.hashPassword(password);     const isValid = await AuthService.comparePassword(password, hash);    expect(isValid).toBe(true);     const isInvalid = await AuthService.comparePassword('wrongPassword', hash);    expect(isInvalid).toBe(false);  });   it('should generate valid JWT token', () => {    const token = AuthService.generateToken({      userId: '123',      email: 'test@example.com',    });     expect(token).toBeTruthy();     const decoded = AuthService.verifyToken(token);    expect(decoded).toMatchObject({      userId: '123',      email: 'test@example.com',    });  });});``` ## Production Deployment ### Docker Setup **Dockerfile**:```dockerfileFROM node:20-alpine AS builder WORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run build FROM node:20-alpine WORKDIR /appCOPY package*.json ./RUN npm ci --omit=devCOPY --from=builder /app/dist ./dist ENV NODE_ENV=productionEXPOSE 3000 CMD ["node", "dist/server.js"]``` **docker-compose.yml**:```yamlversion: '3.8' services:  app:    build: .    ports:      - "3000:3000"    environment:      - DATABASE_URL=postgresql://user:pass@db:5432/mydb      - REDIS_URL=redis://redis:6379      - JWT_SECRET=${JWT_SECRET}    depends_on:      - db      - redis   db:    image: postgres:16-alpine    environment:      - POSTGRES_USER=user      - POSTGRES_PASSWORD=pass      - POSTGRES_DB=mydb    volumes:      - postgres_data:/var/lib/postgresql/data   redis:    image: redis:7-alpine    volumes:      - redis_data:/data volumes:  postgres_data:  redis_data:``` ### PM2 Clustering **ecosystem.config.js**:```javascriptmodule.exports = {  apps: [{    name: 'api',    script: './dist/server.js',    instances: 'max',    exec_mode: 'cluster',    env: {      NODE_ENV: 'production',    },    error_file: './logs/err.log',    out_file: './logs/out.log',    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',  }],};``` ## Best Practices ### Project Structure```src/├── server.ts              # Entry point├── config/│   └── env.ts            # Environment config├── routes/│   ├── index.ts          # Route aggregator│   ├── users.ts│   └── posts.ts├── middleware/│   ├── auth.ts│   ├── validation.ts│   └── errorHandler.ts├── services/│   ├── auth.ts│   └── user.ts├── repositories/│   └── userRepository.ts├── db/│   ├── client.ts│   └── schema.ts├── types/│   └── index.ts└── tests/    ├── setup.ts    ├── users.test.ts    └── auth.test.ts``` ### Key Principles- **Separation of Concerns**: Routes → Controllers → Services → Repositories- **Type Safety**: Use TypeScript strict mode, Zod for runtime validation- **Error Handling**: Centralized error handler, custom error classes- **Security**: Helmet, rate limiting, input validation, CORS- **Logging**: Structured logging (pino, winston), request IDs- **Testing**: Unit tests for services, integration tests for APIs- **Documentation**: OpenAPI/Swagger for API documentation ### Express vs Fastify **Use Express when**:- Large ecosystem of middleware needed- Team familiarity is priority- Prototype/MVP development- Legacy codebase compatibility **Use Fastify when**:- Performance is critical (2-3x faster)- Type safety is important (built-in TypeScript support)- Schema validation required (JSON Schema built-in)- Modern async/await patterns preferred- Plugin architecture needed ### Performance Tips- Use connection pooling for databases- Implement caching (Redis, in-memory)- Enable compression (gzip, brotli)- Use clustering for CPU-intensive tasks- Implement rate limiting- Optimize database queries (indexes, query analysis)- Use CDN for static assets- Enable HTTP/2 in production