Smithery Logo
MCPsSkillsDocsPricing
Login
NewFlame, an assistant that learns and improves. Available onTelegramSlack
    bobmatnyc

    nodejs-backend-typescript

    bobmatnyc/nodejs-backend-typescript
    Coding
    10

    About

    SKILL.md

    Install

    • Telegram
      Telegram
    • Slack
      Slack
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    • Download skill
    ├─
    ├─
    └─
    Smithery Logo

    Give agents more agency

    Resources

    DocumentationPrivacy PolicySystem Status

    Company

    PricingAboutBlog

    Connect

    © 2026 Smithery. All rights reserved.

    About

    Node.js backend development with TypeScript, Express/Fastify servers, routing, middleware, and database integration

    SKILL.md

    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):

    {
      "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:

    {
      "scripts": {
        "dev": "tsx watch src/server.ts",
        "build": "tsc",
        "start": "node dist/server.js",
        "test": "vitest"
      }
    }
    

    Development Dependencies

    npm install -D typescript @types/node tsx vitest
    npm install -D @types/express  # or @types/node (Fastify has built-in types)
    

    Express Patterns

    Basic Express Server

    src/server.ts:

    import express, { Request, Response, NextFunction } from 'express';
    import { z } from 'zod';
    
    const app = express();
    const port = process.env.PORT || 3000;
    
    // Middleware
    app.use(express.json());
    app.use(express.urlencoded({ extended: true }));
    
    // Type-safe request handlers
    interface TypedRequest<T> extends Request {
      body: T;
    }
    
    // Routes
    app.get('/health', (req: Request, res: Response) => {
      res.json({ status: 'ok', timestamp: new Date().toISOString() });
    });
    
    // Start server
    app.listen(port, () => {
      console.log(`Server running on port ${port}`);
    });
    

    Router Pattern

    src/routes/users.ts:

    import { 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:

    import { 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:

    import { 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:

    import { 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:

    import 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 validation
    fastify.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:

    import { 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:

    import { 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

    import { 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

    import { 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:

    import 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)

    import 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:

    import { 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:

    import { 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:

    import { 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:

    datasource 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:

    import { 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:

    import { 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:

    const 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

    interface 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:

    import { 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:

    import { env } from './config/env';
    
    const port = env.PORT; // Type-safe, validated
    

    Testing

    Vitest Setup

    vitest.config.ts:

    import { 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:

    import { 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:

    import { 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:

    FROM node:20-alpine AS builder
    
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci
    COPY . .
    RUN npm run build
    
    FROM node:20-alpine
    
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --omit=dev
    COPY --from=builder /app/dist ./dist
    
    ENV NODE_ENV=production
    EXPOSE 3000
    
    CMD ["node", "dist/server.js"]
    

    docker-compose.yml:

    version: '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:

    module.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
    Recommended Servers
    Supabase
    Supabase
    ThinAir Data
    ThinAir Data
    PlanetScale
    PlanetScale
    Repository
    bobmatnyc/claude-mpm-skills
    Files