Skip to content
Back to skills

1936 Express Api Scaffold 2e4eb9a7

ASecurity

Generate production-ready Express.js REST API with TypeScript and auth

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
databasestypescriptgobashsqlnodeexpressfastapidockertestinggit

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1936-express-api-scaffold_2e4eb9a7 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1936 Express Api Scaffold 2e4eb9a7?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1936 Express Api Scaffold 2e4eb9a7
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1936-express-api-scaffold-2e4eb9a7/badge)](https://www.skillsdirectory.com/skills/tools-only-1936-express-api-scaffold-2e4eb9a7)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
name: express-api-scaffold
description: >
  Generate production-ready Express.js REST API with TypeScript and auth
shortcut: eas
category: backend
difficulty: intermediate
estimated_time: 5-10 minutes
---
# Express API Scaffold

Generates a complete Express.js REST API boilerplate with TypeScript, authentication, database integration, and testing setup.

## What This Command Does

**Generated Project:**
-  Express.js with TypeScript
-  JWT authentication
-  Database integration (Prisma or TypeORM)
-  Input validation (Zod)
-  Error handling middleware
-  Rate limiting & security (Helmet, CORS)
-  Testing setup (Jest + Supertest)
-  Docker configuration
-  Example CRUD endpoints

**Output:** Complete API project ready for development

**Time:** 5-10 minutes

---

## Usage

```bash
# Generate full Express API
/express-api-scaffold "Task Management API"

# Shortcut
/eas "E-commerce API"

# With specific database
/eas "Blog API" --database postgresql

# With authentication type
/eas "Social API" --auth jwt --database mongodb
```

---

## Example Output

**Input:**
```
/eas "Task Management API" --database postgresql
```

**Generated Project Structure:**
```
task-api/
├── src/
│   ├── controllers/        # Request handlers
│   │   ├── auth.controller.ts
│   │   └── task.controller.ts
│   ├── middleware/         # Express middleware
│   │   ├── auth.middleware.ts
│   │   ├── error.middleware.ts
│   │   └── validation.middleware.ts
│   ├── models/            # Database models
│   │   └── task.model.ts
│   ├── routes/            # API routes
│   │   ├── auth.routes.ts
│   │   └── task.routes.ts
│   ├── services/          # Business logic
│   │   ├── auth.service.ts
│   │   └── task.service.ts
│   ├── utils/             # Utilities
│   │   ├── jwt.util.ts
│   │   └── password.util.ts
│   ├── config/            # Configuration
│   │   └── database.ts
│   ├── types/             # TypeScript types
│   │   └── express.d.ts
│   ├── app.ts             # Express app setup
│   └── server.ts          # Server entry point
├── tests/
│   ├── auth.test.ts
│   └── task.test.ts
├── prisma/
│   └── schema.prisma      # Database schema
├── .env.example
├── .gitignore
├── package.json
├── tsconfig.json
├── jest.config.js
├── Dockerfile
├── docker-compose.yml
└── README.md
```

---

## Generated Files

### 1. **src/server.ts** (Entry Point)

```typescript
import app from './app'
import { config } from './config'

const PORT = process.env.PORT || 3000

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
  console.log(`Environment: ${process.env.NODE_ENV}`)
})
```

### 2. **src/app.ts** (Express Setup)

```typescript
import express, { Application } from 'express'
import cors from 'cors'
import helmet from 'helmet'
import morgan from 'morgan'
import rateLimit from 'express-rate-limit'

import authRoutes from './routes/auth.routes'
import taskRoutes from './routes/task.routes'
import { errorHandler } from './middleware/error.middleware'
import { notFoundHandler } from './middleware/notFound.middleware'

const app: Application = express()

// Security middleware
app.use(helmet())
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
  credentials: true
}))

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  message: 'Too many requests, please try again later'
})
app.use('/api/', limiter)

// Parsing middleware
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// Logging
if (process.env.NODE_ENV !== 'test') {
  app.use(morgan('combined'))
}

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() })
})

// Routes
app.use('/api/auth', authRoutes)
app.use('/api/tasks', taskRoutes)

// Error handling
app.use(notFoundHandler)
app.use(errorHandler)

export default app
```

### 3. **src/controllers/auth.controller.ts**

```typescript
import { Request, Response, NextFunction } from 'express'
import { AuthService } from '../services/auth.service'
import { ApiError } from '../utils/ApiError'

const authService = new AuthService()

export class AuthController {
  async register(req: Request, res: Response, next: NextFunction) {
    try {
      const { email, password, name } = req.body

      const result = await authService.register({ email, password, name })

      res.status(201).json({
        data: {
          user: result.user,
          token: result.token
        }
      })
    } catch (error) {
      next(error)
    }
  }

  async login(req: Request, res: Response, next: NextFunction) {
    try {
      const { email, password } = req.body

      const result = await authService.login(email, password)

      res.json({
        data: {
          user: result.user,
          token: result.token
        }
      })
    } catch (error) {
      next(error)
    }
  }

  async getProfile(req: Request, res: Response, next: NextFunction) {
    try {
      const userId = req.user!.id

      const user = await authService.getUserById(userId)

      res.json({ data: user })
    } catch (error) {
      next(error)
    }
  }
}
```

### 4. **src/middleware/auth.middleware.ts**

```typescript
import { Request, Response, NextFunction } from 'express'
import jwt from 'jsonwebtoken'
import { ApiError } from '../utils/ApiError'

interface JwtPayload {
  userId: string
  email: string
}

declare global {
  namespace Express {
    interface Request {
      user?: {
        id: string
        email: string
      }
    }
  }
}

export function authenticate(req: Request, res: Response, next: NextFunction) {
  try {
    const authHeader = req.headers.authorization

    if (!authHeader?.startsWith('Bearer ')) {
      throw new ApiError(401, 'No token provided')
    }

    const token = authHeader.split(' ')[1]

    const decoded = jwt.verify(
      token,
      process.env.JWT_SECRET!
    ) as JwtPayload

    req.user = {
      id: decoded.userId,
      email: decoded.email
    }

    next()
  } catch (error) {
    if (error instanceof jwt.JsonWebTokenError) {
      next(new ApiError(401, 'Invalid token'))
    } else {
      next(error)
    }
  }
}
```

### 5. **src/middleware/error.middleware.ts**

```typescript
import { Request, Response, NextFunction } from 'express'
import { ApiError } from '../utils/ApiError'
import { ZodError } from 'zod'

export function errorHandler(
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) {
  console.error('Error:', err)

  // Handle known API errors
  if (err instanceof ApiError) {
    return res.status(err.statusCode).json({
      error: {
        code: err.name,
        message: err.message,
        ...(err.details && { details: err.details })
      }
    })
  }

  // Handle validation errors (Zod)
  if (err instanceof ZodError) {
    return res.status(400).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Validation failed',
        details: err.errors.map(e => ({
          field: e.path.join('.'),
          message: e.message
        }))
      }
    })
  }

  // Handle unexpected errors
  res.status(500).json({
    error: {
      code: 'INTERNAL_SERVER_ERROR',
      message: process.env.NODE_ENV === 'production'
        ? 'An unexpected error occurred'
        : err.message
    }
  })
}
```

### 6. **src/routes/task.routes.ts**

```typescript
import { Router } from 'express'
import { TaskController } from '../controllers/task.controller'
import { authenticate } from '../middleware/auth.middleware'
import { validate } from '../middleware/validation.middleware'
import { createTaskSchema, updateTaskSchema } from '../schemas/task.schema'

const router = Router()
const taskController = new TaskController()

// All routes require authentication
router.use(authenticate)

router.get('/', taskController.list)
router.post('/', validate(createTaskSchema), taskController.create)
router.get('/:id', taskController.getById)
router.patch('/:id', validate(updateTaskSchema), taskController.update)
router.delete('/:id', taskController.delete)

export default router
```

### 7. **src/services/task.service.ts**

```typescript
import { PrismaClient } from '@prisma/client'
import { ApiError } from '../utils/ApiError'

const prisma = new PrismaClient()

export class TaskService {
  async create(userId: string, data: { title: string; description?: string }) {
    return await prisma.task.create({
      data: {
        ...data,
        userId
      }
    })
  }

  async findAll(userId: string) {
    return await prisma.task.findMany({
      where: { userId },
      orderBy: { createdAt: 'desc' }
    })
  }

  async findById(id: string, userId: string) {
    const task = await prisma.task.findUnique({
      where: { id }
    })

    if (!task) {
      throw new ApiError(404, 'Task not found')
    }

    if (task.userId !== userId) {
      throw new ApiError(403, 'Access denied')
    }

    return task
  }

  async update(id: string, userId: string, data: Partial<{ title: string; description: string; completed: boolean }>) {
    await this.findById(id, userId) // Check ownership

    return await prisma.task.update({
      where: { id },
      data
    })
  }

  async delete(id: string, userId: string) {
    await this.findById(id, userId) // Check ownership

    await prisma.task.delete({
      where: { id }
    })
  }
}
```

### 8. **prisma/schema.prisma** (Database Schema)

```prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  password  String
  name      String
  tasks     Task[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("users")
}

model Task {
  id          String   @id @default(uuid())
  title       String
  description String?
  completed   Boolean  @default(false)
  userId      String
  user        User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([userId])
  @@map("tasks")
}
```

### 9. **tests/task.test.ts** (Integration Tests)

```typescript
import request from 'supertest'
import app from '../src/app'
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

describe('Task API', () => {
  let authToken: string
  let userId: string

  beforeAll(async () => {
    // Create test user and get token
    const res = await request(app)
      .post('/api/auth/register')
      .send({
        email: '[email protected]',
        password: 'password123',
        name: 'Test User'
      })

    authToken = res.body.data.token
    userId = res.body.data.user.id
  })

  afterAll(async () => {
    // Cleanup
    await prisma.task.deleteMany({ where: { userId } })
    await prisma.user.delete({ where: { id: userId } })
    await prisma.$disconnect()
  })

  describe('POST /api/tasks', () => {
    it('should create a new task', async () => {
      const res = await request(app)
        .post('/api/tasks')
        .set('Authorization', `Bearer ${authToken}`)
        .send({
          title: 'Test Task',
          description: 'Test description'
        })

      expect(res.status).toBe(201)
      expect(res.body.data).toHaveProperty('id')
      expect(res.body.data.title).toBe('Test Task')
    })

    it('should require authentication', async () => {
      const res = await request(app)
        .post('/api/tasks')
        .send({ title: 'Test' })

      expect(res.status).toBe(401)
    })
  })

  describe('GET /api/tasks', () => {
    it('should list user tasks', async () => {
      const res = await request(app)
        .get('/api/tasks')
        .set('Authorization', `Bearer ${authToken}`)

      expect(res.status).toBe(200)
      expect(Array.isArray(res.body.data)).toBe(true)
    })
  })
})
```

### 10. **package.json**

```json
{
  "name": "task-api",
  "version": "1.0.0",
  "scripts": {
    "dev": "ts-node-dev --respawn --transpile-only src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js",
    "test": "jest --coverage",
    "lint": "eslint src/**/*.ts",
    "format": "prettier --write \"src/**/*.ts\"",
    "db:migrate": "prisma migrate dev",
    "db:push": "prisma db push",
    "db:generate": "prisma generate"
  },
  "dependencies": {
    "express": "^4.18.2",
    "cors": "^2.8.5",
    "helmet": "^7.1.0",
    "express-rate-limit": "^7.1.5",
    "morgan": "^1.10.0",
    "bcrypt": "^5.1.1",
    "jsonwebtoken": "^9.0.2",
    "zod": "^3.22.4",
    "@prisma/client": "^5.8.0",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^20.10.6",
    "@types/cors": "^2.8.17",
    "@types/morgan": "^1.9.9",
    "@types/bcrypt": "^5.0.2",
    "@types/jsonwebtoken": "^9.0.5",
    "@types/jest": "^29.5.11",
    "@types/supertest": "^6.0.2",
    "typescript": "^5.3.3",
    "ts-node-dev": "^2.0.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.1.1",
    "supertest": "^6.3.3",
    "prisma": "^5.8.0",
    "eslint": "^8.56.0",
    "prettier": "^3.1.1"
  }
}
```

---

## Features

**Security:**
- Helmet.js for HTTP headers
- CORS with configurable origins
- Rate limiting (100 req/15min)
- JWT authentication
- Password hashing (bcrypt)
- Input validation (Zod)

**Database:**
- Prisma ORM with TypeScript
- Automatic migrations
- Type-safe queries
- Supports PostgreSQL, MySQL, SQLite

**Testing:**
- Jest + Supertest
- Integration tests
- Coverage reporting
- Test database isolation

**Development:**
- Hot reload (ts-node-dev)
- TypeScript with strict mode
- ESLint + Prettier
- Environment variables

**Production:**
- Docker support
- Health check endpoint
- Error logging
- Graceful shutdown

---

## Getting Started

**1. Install dependencies:**
```bash
npm install
```

**2. Configure environment:**
```bash
cp .env.example .env
# Edit .env with your database URL and secrets
```

**3. Run database migrations:**
```bash
npm run db:migrate
```

**4. Start development server:**
```bash
npm run dev
```

**5. Run tests:**
```bash
npm test
```

---

## Related Commands

- `/fastapi-scaffold` - Generate FastAPI boilerplate
- Backend Architect (agent) - Architecture review
- API Builder (agent) - API design guidance

---

**Build production-ready APIs. Ship faster. Scale confidently.** 

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.