Install
Terminal · npx$
npx skills add https://github.com/obra/superpowers --skill brainstormingWorks 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 packSource file
SKILL.md1030 linesExpandCollapse
---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