npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

create-xpress-backend

v1.0.2

Published

Scaffold a production-ready Express + Prisma + PostgreSQL backend in seconds

Readme

create-xpress-backend

Scaffold a production-ready Express + Prisma + PostgreSQL backend in seconds.
Choose TypeScript (default) or JavaScript — everything wired up, zero boilerplate.

npx create-xpress-backend

Table of Contents


Requirements

| Requirement | Minimum | Check | |-------------|---------|-------| | Node.js | ≥ 18.0.0 | node -v | | npm | ≥ 8.0.0 | npm -v | | PostgreSQL | ≥ 13 | running locally or on a cloud service |

PostgreSQL can be local, Docker, or a hosted service — Supabase, Neon, Railway, Render, etc.


How to Use the CLI

No global install needed. Just run:

npx create-xpress-backend

You will see the banner and then a short series of prompts:

  ┌─────────────────────────────────────────────┐
  │  🚀  create-xpress-backend  v1.0.0          │
  │  Express · Prisma · PostgreSQL · Node.js     │
  └─────────────────────────────────────────────┘

  project_name          :  my-api
  language              :  ❯ TypeScript  (recommended)
                             JavaScript
  port                  :  5050
  file upload system    :  include? (Y/n)
  JWT auth module       :  include? (y/N)
  git init              :  initialize? (Y/n)

Prompt Reference

| Prompt | What it does | Default | |--------|-------------|---------| | project_name | Folder name + npm package name. Lowercase letters, numbers, hyphens, underscores only. | — | | language | TypeScript or JavaScript. Arrow-key selection. | TypeScript | | port | Port written into .env and used in startup logs. | 5050 | | file upload system | Adds Multer, an /uploads directory, and upload/list/delete routes. | Yes | | JWT auth module | Adds register + login endpoints and a JWT guard middleware. | No | | git init | Runs git init inside the generated folder. | Yes |

After confirming, the CLI scaffolds everything and prints:

  ✔ Done! Project "my-api" created! (TypeScript)

  Get started:
    cd my-api
    npm install
    # configure DATABASE_URL in .env, then:
    npm run db:push
    npm run dev

  Build for production:
    npm run build
    npm start

  Health check  →  http://localhost:5050/health
  Prisma Studio →  npm run db:studio

TypeScript vs JavaScript

The CLI generates a complete, properly typed codebase for whichever language you pick. There is no "TS wrapper around JS" — every file is written natively.

| | TypeScript | JavaScript | |--|-----------|-----------| | Source files | .ts | .js | | Module system | ES module imports (import/export) | CommonJS (require/module.exports) | | Dev server | ts-node-dev | nodemon | | Build step | tscdist/ | None — runs directly | | Start (prod) | node dist/server.js | node src/server.js | | Type checking | ✅ Strict mode | — | | tsconfig.json | ✅ Included | — | | src/types/express.d.ts | ✅ Included (req.user typed) | — | | Type packages | @types/* included | — |

Recommendation: Use TypeScript. It catches bugs at compile time and makes refactoring safer — especially important as your API grows.


What Gets Scaffolded

Depending on your choices, between 17 and 25 files are generated:

| Language | Base | + Upload | + Auth | + Both | |----------|------|----------|--------|--------| | TypeScript | 19 | 20 | 24 | 25 | | JavaScript | 17 | 18 | 22 | 23 |


Project Structure Explained

TypeScript

my-api/
├── prisma/
│   └── schema.prisma              ← database models
├── src/
│   ├── app.ts                     ← Express config (middleware, routes, error handler)
│   ├── server.ts                  ← HTTP server + graceful shutdown
│   ├── types/
│   │   └── express.d.ts           ← augments req.user with JwtPayload type
│   ├── database/
│   │   ├── prisma.ts              ← shared PrismaClient singleton
│   │   └── seed.ts                ← database seeder
│   ├── middleware/
│   │   ├── validate.ts            ← Joi validation wrapper
│   │   ├── upload.ts              ← Multer config (if uploads selected)
│   │   └── auth.ts                ← JWT guard middleware (if auth selected)
│   └── modules/
│       ├── index.ts               ← central router
│       ├── demo/                  ← example CRUD module
│       │   ├── demo.controller.ts
│       │   ├── demo.service.ts
│       │   ├── demo.routes.ts
│       │   └── demo.validation.ts
│       └── auth/                  ← auth module (if auth selected)
│           ├── auth.controller.ts
│           ├── auth.service.ts
│           ├── auth.routes.ts
│           └── auth.validation.ts
├── uploads/                       ← file storage (if uploads selected)
├── tsconfig.json
├── prisma.config.ts
├── .env
├── .env.example
├── .gitignore
└── package.json

JavaScript

Same structure but with .js extensions, require/module.exports, no tsconfig.json, no types/ folder.


Every File — What It Does

src/server.ts / src/server.js

Entry point. Creates an http.Server around the Express app, starts listening on PORT, logs startup info, and handles SIGINT/SIGTERM for graceful shutdown with a 5-second forced-exit fallback.

src/app.ts / src/app.js

Configures the Express app — does not start the server. Middleware stack applied in order:

  1. helmet() — secure HTTP headers
  2. cors() — CORS from CLIENT_URL
  3. morgan('dev') — request logging
  4. express.json() — JSON body parsing (10 MB limit)
  5. express.urlencoded() — form body parsing
  6. /uploads static route — serves uploaded files (if uploads enabled)
  7. /api — all module routes
  8. /health — health check
  9. 404 handler
  10. Central error handler — anything passed to next(error) lands here

src/database/prisma.ts / prisma.js

Creates and exports a single shared PrismaClient using the @prisma/adapter-pg driver adapter with a pg connection pool. Every module imports from here.

src/database/seed.ts / seed.js

Standalone seeder. Safe to run multiple times — checks for existing records before inserting. Extend this to seed any initial data your app needs.

prisma/schema.prisma

Defines your database models. Base includes User. If file uploads were selected, File is also included with a User relation. Edit here, then run npm run db:push.

src/types/express.d.ts (TypeScript only)

Augments Express's Request interface to add user?: string | JwtPayload. This is what makes req.user type-safe in every controller and middleware.

src/middleware/validate.ts / validate.js

Takes a Joi schema, returns Express middleware. On failure, returns 400 with the first validation error. Usage:

router.post('/', validate(createSchema), controller.create);

src/middleware/upload.ts / upload.js (if uploads selected)

Multer configured with disk storage, unique filenames, file type filtering (JPEG/PNG/GIF/PDF/TXT/DOC/DOCX), and size limit from MAX_FILE_SIZE env var.

src/middleware/auth.ts / auth.js (if auth selected)

Reads Authorization: Bearer <token>, verifies against JWT_SECRET, attaches decoded payload to req.user. Returns 401 for missing or invalid tokens.

router.get('/me', authMiddleware, controller.getMe);

src/modules/index.ts / index.js

The central router. Add one line per new module:

router.use('/users', usersRoutes);

Demo Module (src/modules/demo/)

Working example of the Controller → Service → Routes → Validation pattern:

  • Validation — Joi schemas for create and update
  • Routes — maps HTTP verbs + paths to controller methods, attaches middleware
  • Controller — handles req/res, calls service, passes errors to next()
  • Service — all Prisma calls live here, returns plain data, throws on error

This is your template module. Copy it, rename everything, and replace the Prisma calls for your first real feature.

Auth Module (src/modules/auth/) (if selected)

  • Register — hashes password (bcrypt, 10 rounds), creates user, returns user without password field
  • Login — finds user, compares password, signs JWT, returns { user, token }

Environment Variables

Your .env is pre-filled when the project is created:

NODE_ENV=development
PORT=5050

# Database — edit to match your Postgres instance
DATABASE_URL="postgresql://postgres:admin@localhost:5432/my_api_db"
# Format: postgresql://USER:PASSWORD@HOST:PORT/DATABASE_NAME

# JWT — change JWT_SECRET to a long random string before deploying
JWT_SECRET=your-super-secret-jwt-key-change-this
JWT_EXPIRES_IN=7d        # 7d | 24h | 3600 (seconds)

# File Upload (if included)
MAX_FILE_SIZE=10         # megabytes

# CORS
CLIENT_URL=http://localhost:3000
SERVER_URL=http://localhost:5050

.env is git-ignored. Commit .env.example — it shows required keys without real values.


First-Time Setup (Step by Step)

# 1. Scaffold
npx create-xpress-backend

# 2. Enter your project
cd my-api

# 3. Install dependencies
npm install

# 4. Create the database (if it doesn't exist yet)
#    psql -U postgres -c "CREATE DATABASE my_api_db;"

# 5. Set DATABASE_URL in .env
#    DATABASE_URL="postgresql://postgres:yourpassword@localhost:5432/my_api_db"

# 6. Push schema to DB + generate Prisma Client
npm run db:push

# 7. (Optional) Seed initial data
npm run db:seed

# 8. Start dev server
npm run dev

Visit http://localhost:5050/health:

{
  "status": "OK",
  "message": "API Running ✅",
  "timestamp": "2026-07-09T10:00:00.000Z",
  "uptime": 3.2
}

Available Scripts

TypeScript project

| Command | Description | |---------|-------------| | npm run dev | Start with ts-node-dev (auto-restarts + transpiles on save) | | npm run build | Compile TypeScript → dist/ | | npm start | Run compiled production build (node dist/server.js) | | npm run db:push | Push schema changes + regenerate Prisma Client | | npm run db:generate | Regenerate Prisma Client without pushing | | npm run db:dev | db:push + db:generate in one command | | npm run db:seed | Run the database seeder | | npm run db:studio | Open Prisma Studio | | npm run db:reset | ⚠️ Force-reset database (drops all data) |

JavaScript project

Same as above except:

  • npm run dev uses nodemon
  • No npm run build — runs directly
  • npm start runs node src/server.js

API Reference

Health

GET /health
{ "status": "OK", "message": "API Running ✅", "timestamp": "...", "uptime": 120.4 }

Demo — CRUD (always included)

Create

POST /api/demo
Content-Type: application/json

{ "name": "Alice", "email": "[email protected]" }

Returns 201 with the created record.

Get All

GET /api/demo

Returns 200 with array of records (newest first).

Get by ID

GET /api/demo/:id

Returns 200 with the record, or 404.

Update

PUT /api/demo/:id
Content-Type: application/json

{ "name": "Alice Updated" }

Returns 200 with updated record.

Delete

DELETE /api/demo/:id

Returns 200 with { "message": "Demo deleted successfully" }.


File Upload (if selected)

Upload

POST /api/demo/upload
Content-Type: multipart/form-data

field: file

Returns 201 with the stored file record.

List

GET /api/demo/files/all

Returns 200 with array of file records.

Delete

DELETE /api/demo/files/:fileId

Deletes physical file from disk + database record.

Allowed types: JPEG, PNG, GIF, PDF, TXT, DOC, DOCX
Max size: MAX_FILE_SIZE MB (default 10)


Auth (if selected)

Register

POST /api/auth/register
Content-Type: application/json

{ "name": "Alice", "email": "[email protected]", "password": "secret123" }

Returns 201 with user object (no password field).

Login

POST /api/auth/login
Content-Type: application/json

{ "email": "[email protected]", "password": "secret123" }

Returns 200 with:

{
  "success": true,
  "data": {
    "user": { "id": "...", "name": "Alice", "email": "[email protected]" },
    "token": "eyJhbGci..."
  }
}

Use the token on protected routes:

Authorization: Bearer eyJhbGci...

How the Architecture Works

Every request flows through the same layers in the same order:

Incoming Request
  │
  ▼
Middleware Stack  (app.ts)
  helmet → cors → morgan → body-parser
  │
  ▼
Module Router  (src/modules/index.ts)
  /api/demo  →  demo.routes.ts
  /api/auth  →  auth.routes.ts
  │
  ▼
Route  (*.routes.ts)
  validate(schema) → authMiddleware → controller.method
  │
  ▼
Controller  (*.controller.ts)
  calls service → formats JSON → passes errors to next()
  │
  ▼
Service  (*.service.ts)
  all Prisma / database logic
  throws typed errors
  │
  ▼
Central Error Handler  (app.ts)
  catches everything passed to next(error)
  returns { success: false, message }

Key rules:

  • Controllers never touch the database — that belongs in the service
  • Services never touch req/res — they work with plain data and types
  • Validation runs before the controller — invalid requests are rejected early
  • All errors bubble to one place — the central error handler in app.ts

Adding a New Module

Example: adding a products module.

1. Add a Prisma model

model Product {
  id          String   @id @default(uuid())
  name        String
  price       Float
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

2. Push the schema

npm run db:push

3. Create the module folder

src/modules/products/
├── products.validation.ts
├── products.service.ts
├── products.controller.ts
└── products.routes.ts

4. Write the service (Prisma calls go here)

import prisma from '../../database/prisma';

class ProductsService {
  async getAll() {
    return prisma.product.findMany({ orderBy: { createdAt: 'desc' } });
  }
}
export default new ProductsService();

5. Register the route in src/modules/index.ts

import productsRoutes from './products/products.routes';
router.use('/products', productsRoutes);

GET /api/products is now live.


File Upload System

Client → multipart/form-data (field: "file")
  │
  ▼
upload.single('file')            ← Multer middleware
  validates type + size
  writes to  uploads/<name>-<timestamp>-<random>.<ext>
  attaches to req.file
  │
  ▼
Controller reads req.file
  passes userId + file to service
  │
  ▼
Service creates File record in database
  stores: filename, originalName, path, size, mimetype, userId
  │
  ▼
File on disk:  uploads/file-1720524000000-123456789.jpg
DB record in:  files  table
Accessible at: GET /uploads/<filename>

To restrict file types, edit allowedMimes in src/middleware/upload.ts.
To change max size, update MAX_FILE_SIZE in .env (value in MB).


JWT Auth System

POST /api/auth/register
  validate { name, email, password }
  check if email exists   → 409 if yes
  bcrypt.hash(password, 10)
  prisma.user.create(...)
  return user (password stripped)

POST /api/auth/login
  validate { email, password }
  find user by email      → 401 if not found
  bcrypt.compare(...)     → 401 if wrong
  jwt.sign({ id, email }, JWT_SECRET, { expiresIn })
  return { user, token }

Protected Route
  Authorization: Bearer <token>
  authMiddleware verifies token
  attaches decoded payload → req.user
  → 401 if missing or invalid

To protect a route:

// TypeScript
import authMiddleware from '../../middleware/auth';
router.get('/me', authMiddleware, controller.getMe.bind(controller));
// JavaScript
const authMiddleware = require('../../middleware/auth');
router.get('/me', authMiddleware, controller.getMe);

Inside the controller, req.user will contain { id, email, iat, exp }.


Database Workflows

Iterating on your schema

# 1. Edit prisma/schema.prisma
# 2. Push + regenerate
npm run db:push
# 3. Browse your data
npm run db:studio

Reset for a clean slate (dev only)

npm run db:reset   # ⚠️ drops all data
npm run db:seed    # re-seed if needed

Connecting to a hosted database

# Supabase
DATABASE_URL="postgresql://postgres:[password]@db.[ref].supabase.co:5432/postgres"

# Neon
DATABASE_URL="postgresql://[user]:[password]@[host].neon.tech/[db]?sslmode=require"

# Railway
DATABASE_URL="postgresql://postgres:[password]@[host].railway.app:5432/railway"

Deployment Checklist

  • [ ] NODE_ENV=production set in hosting environment
  • [ ] JWT_SECRET is a long, random, unique string — never the default
  • [ ] CLIENT_URL set to your real frontend domain (not *)
  • [ ] DATABASE_URL points to production database
  • [ ] npm run db:push run against the production database
  • [ ] TypeScript only: npm run build run before deploy — serve from dist/
  • [ ] uploads/ is on persistent storage (not ephemeral filesystem)
  • [ ] Demo routes removed or protected before go-live

Production start:

# TypeScript
npm run build && npm start

# JavaScript
npm start

Minimal Dockerfile (TypeScript):

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 5050
CMD ["npm", "start"]

FAQ

Q: Which should I pick — TypeScript or JavaScript?
A: TypeScript is the default for a reason. Strict typing catches bugs before runtime, makes refactoring reliable, and improves IDE autocomplete. If you're already comfortable with TS, pick it. If you're learning or need a quick prototype, JS is perfectly fine.

Q: What does ts-node-dev do?
A: It's the TypeScript equivalent of nodemon — it watches .ts files, transpiles them on the fly, and restarts the server on changes. No manual tsc step during development.

Q: Do I need to run npm run build during development?
A: No. npm run dev uses ts-node-dev which runs TypeScript directly. npm run build is only needed when preparing for production.

Q: Can I use MySQL or SQLite instead of PostgreSQL?
A: The generated project uses @prisma/adapter-pg (PostgreSQL-specific). To switch, remove the adapter, change provider in schema.prisma, and replace the pg pool. Prisma supports MySQL, SQLite, MongoDB, and others.

Q: Why is there a demo module instead of a real resource?
A: It's intentionally generic so it doesn't clash with your actual domain models. It shows the pattern — rename and replace it for your first real feature.

Q: How do I add the password field if I didn't choose JWT auth?
A: Add it to the User model in schema.prisma:

model User {
  ...
  password  String?
}

Then run npm run db:push.

Q: What is prisma.config.ts / prisma.config.js for?
A: Prisma 7 introduced a new config file format. It tells the Prisma CLI where to find DATABASE_URL. You don't need to edit it.


License

MIT © ayushsolanki29