skills / bobmatnyc / claude-mpm-skills / nodejs-backend-typescript 
nodejs-backend-typescript 
$ npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill nodejs-backend-typescript 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 Weekly Installs 137 Repository bobmatnyc/claude-mpm-skills First Seen Jan 23, 2026 Security Audits Gen Agent Trust Hub Pass Socket Pass Snyk Pass Installed on claude-code 105 opencode 103 gemini-cli 89 codex 87 github-copilot 78 antigravity 76