A fast and opinionated CLI for scaffolding Express.js projects with TypeScript, Prisma, and security-first best practices built-in.
Kickpress helps you create production-ready Express.js APIs in seconds, with full CRUD generation, input validation, type safety, and modern tooling.
- β¨ Features
- β‘ Why Kickpress?
- π Requirements
- π Quick Start
- π¦ Installation
- π¨ Templates
- π Command Reference
- π Complete Workflow Example
- π Generated Project Structure
- π¨ Generated Code Examples
- π Security & Validation
- π§ Available Scripts
- π οΈ Technology Stack
- ποΈ Database Support
- π§ͺ Testing Your API
- π Troubleshooting
- β FAQ
- π€ Contributing
- π€ Author
- π License
- π¬ Support
- π Instant Setup - Create a complete project in seconds with auto-installation
- π¨ Multiple Templates - REST API, NPM package, CLI tool, or static Web app
- π Security First - Built-in input validation with Zod on all endpoints
- π¦ TypeScript First - Full TypeScript support with proper types (JavaScript optional)
- ποΈ Optional Database - SQLite or PostgreSQL via Prisma β or no database at all
- π― CRUD Generation - Generate the full stack for any entity in one command
- π Auto-injection - Routes automatically added to your app
- β Add Database Later - Add Prisma to any existing project with
kickpress add db - β‘ Modern Stack - Express, Prisma, Zod, tsx, and express-async-handler
- π‘οΈ Error Handling - Comprehensive error middleware included
- π HTTP Requests - Test files generated for each resource
- βοΈ Zero Configuration - Everything works out of the box
| Task | Manual Setup | With Kickpress |
|---|---|---|
| Project Setup | 15-30 minutes | 30 seconds |
| Prisma Configuration | Manual config | Auto-configured |
| Input Validation | Write from scratch | Auto-generated |
| CRUD Generation | Write from scratch | 1 command |
| Error Handling | Custom impl | Built-in |
| Type Safety | Manual types | Auto-generated |
| Route Registration | Manual import | Auto-injected |
| Add DB Later | Manual wiring | 1 command |
- Node.js: v20.0.0 or higher (for
--env-filesupport) - Package Manager: npm, pnpm, or yarn
- Operating System: macOS, Linux, or Windows
- PostgreSQL: Required only if using the PostgreSQL database option
Note: Kickpress uses Node.js native
--env-fileflag. For Node.js < v20, you may need additional configuration.
# Interactive (recommended for first-time users)
npx kickpress init
# One-liner with all defaults (TypeScript, API template, SQLite, pnpm)
npx kickpress init my-api -y
# Navigate and start
cd my-api
pnpm devYour API is now running at http://localhost:3000! π
No installation required β use npx to run directly:
npx kickpress init my-projectOr install globally:
npm install -g kickpress
kickpress init my-projectChoose a template with -t:
| Template | Description | Database |
|---|---|---|
api |
REST API with Express, Prisma, Zod, and CRUD helpers | Optional |
npm |
Publishable NPM package with TypeScript declarations | None |
cli |
Command-line tool with Commander.js | None |
web |
Static HTML/CSS/JS web app with Express | Optional |
npx kickpress init my-api -t api
npx kickpress init my-lib -t npm
npx kickpress init my-tool -t cli
npx kickpress init my-site -t webCreates a complete project with all dependencies and folder structure.
Syntax:
kickpress init [project-name] [options]Alias: in
Arguments:
project-nameβ Name of your project (optional, will prompt if not provided)
Options:
| Flag | Description |
|---|---|
-t, --template <template> |
Template: api | npm | cli | web |
-d, --database <database> |
Database: sqlite | postgresql | none |
--typescript |
Use TypeScript |
--no-typescript |
Use JavaScript instead |
-p, --package-manager <pm> |
Package manager: pnpm | npm | yarn |
-y, --yes |
Accept all defaults (TypeScript, api, sqlite, pnpm) |
Examples:
# Interactive (prompts for everything)
npx kickpress init
# Accept all defaults β no prompts
npx kickpress init my-api -y
npx kickpress in my-api -y
# API with SQLite
npx kickpress init my-api -t api -d sqlite
# API with PostgreSQL
npx kickpress init my-api -t api -d postgresql
# API with no database
npx kickpress init my-api -t api -d none
# NPM package (no database)
npx kickpress init my-lib -t npm
# CLI tool (no database)
npx kickpress init my-tool -t cli
# JavaScript project
npx kickpress init my-api --no-typescript -t api -d sqliteWhat it does automatically:
- β Creates complete folder structure
- β Generates all configuration files
- β Installs all dependencies
- β Generates Prisma Client and pushes schema (when database is selected)
- β Sets up error handling middleware
- β Configures TypeScript/JavaScript
Generates the full CRUD stack for any entity β model, service, controller, validation, routes, and HTTP test file β all wired together and injected into src/index.*.
Syntax:
kickpress make <entity> [options]Alias: mk
Arguments:
entityβ Name of the entity (singular, e.g.,user,post,product)
Options:
| Flag | Description |
|---|---|
--table <name> |
Table name (plural), skips interactive prompt |
--route <path> |
Route path (e.g. /todos), skips prompt |
-f, --force |
Overwrite existing files |
Examples:
# Interactive (prompts for table name and route path)
npx kickpress make user
npx kickpress make post
# Non-interactive (useful in scripts/CI)
npx kickpress make todo --table todos --route /todos
npx kickpress mk post --table posts --route /postsWhat it generates:
| File | Description |
|---|---|
prisma/schema.prisma |
Updated with new model stub |
types/entity.d.ts |
TypeScript interfaces (TS only) |
models/entity.model.* |
Raw Prisma database operations |
services/entity.service.* |
Business logic wrapping the model |
controllers/entity.controller.* |
HTTP handlers calling the service |
validations/entity.validation.* |
Zod schemas and validation middleware |
routes/entity.routes.* |
Express routes wired to controller + validation |
requests/entity.http |
HTTP test file for all endpoints |
Routes are also auto-injected into src/index.*.
Architecture flow:
Request β Routes β Validation β Controller β Service β Model β Prisma β DB
Each layer has a single responsibility β you add business logic in the service, database queries in the model, and HTTP concerns in the controller.
Wires Prisma into a project that was initially created without a database. All files are patched automatically β you just add your models to prisma/schema.prisma.
Syntax:
kickpress add db [database]Arguments:
databaseβsqliteorpostgresql(optional, will prompt if not provided)
Examples:
# Interactive prompt
npx kickpress add db
# Non-interactive
npx kickpress add db sqlite
npx kickpress add db postgresqlWhat it does:
- β
Installs
@prisma/client,prisma, and the correct database adapter - β
Creates
prisma/schema.prismaandprisma.config.ts - β
Creates
src/lib/prisma.ts(typed Prisma client) - β
Patches
error.middleware.*to handle Prisma error codes (P2002,P2025) - β
Appends
DATABASE_URLto.env - β
Appends Prisma entries to
.gitignore - β
Adds
db:generate,db:push,db:migrate,db:studioscripts topackage.json - β
Runs
db:generateanddb:pushautomatically
Note: This command is safe to run on any Kickpress Express project regardless of template. It will exit early if a database is already configured.
1. Create project:
npx kickpress init blog-api -t api -d sqlite
cd blog-apipnpm users: Run
pnpm approve-buildsand selectbetter-sqlite3before starting.
2. Generate resources:
npx kickpress make post
# Prompts: table name (e.g. posts), route path (e.g. /posts)3. Add fields to your model and validation:
Edit prisma/schema.prisma (generated stub already has id, createdAt, updatedAt):
model Post {
id Int @id @default(autoincrement())
title String
content String
author String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Edit src/validations/post.validation.ts to mirror your fields:
const postCreateSchema = z.object({
title: z.string().min(1).max(255),
content: z.string().min(1),
author: z.string().min(1).max(100),
});Edit src/types/post.d.ts to add the same fields:
export interface Post {
id: number;
title: string;
content: string;
author: string;
createdAt: Date;
updatedAt: Date;
}4. Push schema and start:
pnpm db:generate
pnpm db:push
pnpm dev5. Test your API:
# Get all posts
curl http://localhost:3000/posts
# Create a post
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{"title":"Hello","content":"World","author":"You"}'npx kickpress init blog-api -t api -d postgresql
cd blog-apiEdit .env:
DATABASE_URL="postgresql://postgres:mypassword@localhost:5432/blogdb?schema=public"pnpm db:generate
pnpm db:push
pnpm devnpx kickpress init blog-api -t api -d none
cd blog-api
# ... develop, change your mind ...
npx kickpress add db sqlitemy-api/
βββ src/
β βββ index.ts # Main entry point
β βββ lib/
β β βββ prisma.ts # Prisma client
β βββ controllers/
β β βββ user.controller.ts
β βββ models/
β β βββ user.model.ts
β βββ services/
β β βββ user.service.ts
β βββ routes/
β β βββ user.routes.ts
β βββ validations/
β β βββ user.validation.ts
β βββ types/
β β βββ user.d.ts
β βββ middlewares/
β β βββ error.middleware.ts
β βββ config/
β βββ utils/
βββ prisma/
β βββ schema.prisma
β βββ migrations/
βββ requests/
β βββ user.http
βββ .env
βββ tsconfig.json
βββ package.json
Raw database operations using Prisma. Only the model touches the database directly.
import prisma from "../lib/prisma";
import type { User, UserCreateInput, UserUpdateInput } from "../types/user";
const userFindAll = async (): Promise<User[]> => {
return prisma.user.findMany();
};
const userFindOne = async (id: number): Promise<User | null> => {
return prisma.user.findUnique({ where: { id } });
};
const userCreate = async (data: UserCreateInput): Promise<User> => {
return prisma.user.create({ data });
};
const userUpdate = async (id: number, data: UserUpdateInput): Promise<User | null> => {
return prisma.user.update({ where: { id }, data });
};
const userDelete = async (id: number): Promise<User | null> => {
return prisma.user.delete({ where: { id } });
};
export { userFindAll, userFindOne, userCreate, userUpdate, userDelete };Business logic layer. Controllers call the service β never the model directly. Add your business rules here (e.g. hashing passwords, sending emails, enforcing limits).
import { userFindAll, userFindOne, userCreate, userUpdate, userDelete } from "../models/user.model";
import type { User, UserCreateInput, UserUpdateInput } from "../types/user";
export const getAllUsers = async (): Promise<User[]> => {
return userFindAll();
};
export const getUser = async (id: number): Promise<User | null> => {
return userFindOne(id);
};
export const createUser = async (data: UserCreateInput): Promise<User> => {
return userCreate(data);
};
export const updateUser = async (id: number, data: UserUpdateInput): Promise<User | null> => {
return userUpdate(id, data);
};
export const deleteUser = async (id: number): Promise<User | null> => {
return userDelete(id);
};HTTP layer. Calls the service, handles 404s, sends responses.
import { Request, Response } from "express";
import asyncHandler from "express-async-handler";
import { getAllUsers, getUser, createUser, updateUser, deleteUser } from "../services/user.service";
const all = asyncHandler(async (_: Request, res: Response) => {
const users = await getAllUsers();
res.json(users);
});
const findOne = asyncHandler(async (req: Request, res: Response) => {
const user = await getUser(Number(req.params.id));
if (!user) { res.status(404); throw new Error("User not found"); }
res.json(user);
});
const create = asyncHandler(async (req: Request, res: Response) => {
const user = await createUser(req.body); // body already validated by middleware
res.status(201).json(user);
});
const update = asyncHandler(async (req: Request, res: Response) => {
const user = await getUser(Number(req.params.id));
if (!user) { res.status(404); throw new Error("User not found"); }
const updated = await updateUser(user.id, req.body);
res.json(updated);
});
const remove = asyncHandler(async (req: Request, res: Response) => {
const user = await getUser(Number(req.params.id));
if (!user) { res.status(404); throw new Error("User not found"); }
await deleteUser(user.id);
res.status(204).send();
});
export { all, findOne, create, update, remove };Wires validation middleware to controller handlers.
import { Router } from "express";
import { all, findOne, create, update, remove } from "../controllers/user.controller";
import { validateUserCreate, validateUserUpdate, validateUserId } from "../validations/user.validation";
const router = Router();
router.route("/")
.get(all)
.post(validateUserCreate, create);
router.route("/:id")
.get(validateUserId, findOne)
.patch(validateUserId, validateUserUpdate, update)
.delete(validateUserId, remove);
export default router;Generated with empty schemas and a robust ID validator. Add your fields after extending the Prisma model.
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
const userCreateSchema = z.object({
// Add your fields here after extending the Prisma model
});
const userUpdateSchema = z.object({
// Add your fields here (make them optional)
});
const idParamSchema = z.object({
id: z.string().regex(/^\d+$/, "ID must be a positive integer").transform(Number),
});
// Validation middleware is auto-generated β just fill in the schemas above
export const validateUserCreate = /* ... */;
export const validateUserUpdate = /* ... */;
export const validateUserId = /* ... */;Example after adding fields:
const userCreateSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email().toLowerCase().trim(),
age: z.number().int().positive().optional(),
});β SQL Injection Protection β Prisma uses parameterized queries β Input Validation β Zod validates all request data β Type Coercion β Safe type transformation β Error Information Leakage β Safe error messages in production
After adding fields to your Prisma model, mirror them in src/validations/user.validation.ts:
const userCreateSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email().toLowerCase().trim(),
age: z.number().int().positive().min(18).max(120).optional(),
password: z.string().min(8).regex(/[A-Z]/).regex(/[0-9]/),
});
const userUpdateSchema = z.object({
name: z.string().min(2).max(100).optional(),
email: z.string().email().toLowerCase().trim().optional(),
age: z.number().int().positive().min(18).max(120).optional(),
});# Development
pnpm dev # Start dev server with hot reload
# Build (TypeScript only)
pnpm build # Compile to JavaScript
# Production
pnpm start # Start production server
# Database (when database is configured)
pnpm db:generate # Generate Prisma Client
pnpm db:push # Push schema to database
pnpm db:migrate # Create migration
pnpm db:studio # Open Prisma Studio- Express.js β Fast, minimalist web framework
- TypeScript β Type-safe JavaScript (optional)
- Zod β TypeScript-first schema validation
- tsx β TypeScript execution engine
- express-async-handler β Async error handling
- Prisma β Next-generation ORM
- SQLite β File-based, zero config (with better-sqlite3 adapter)
- PostgreSQL β Production-ready (with @prisma/adapter-pg)
Database is optional for api and web templates and not available for npm and cli templates. You can also add it at any time with kickpress add db.
Best for local development and prototyping. Zero configuration β Kickpress sets it up automatically.
DATABASE_URL="file:./dev.db"Best for production. Requires a running PostgreSQL server and manual DATABASE_URL configuration.
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"Popular hosted options:
# Neon
DATABASE_URL="postgresql://user:pass@ep-cool-name.us-east-2.aws.neon.tech/neondb?sslmode=require"
# Supabase
DATABASE_URL="postgresql://postgres:pass@db.project.supabase.co:5432/postgres?schema=public"
# Railway
DATABASE_URL="postgresql://postgres:pass@containers-us-west.railway.app:5432/railway?schema=public"Use -d none to skip Prisma entirely. The generated project has no Prisma dependencies.
npx kickpress init my-api -t api -d noneYou can always add it later:
npx kickpress add db sqliteEach generated resource includes an .http file. Use the REST Client extension in VS Code:
- Open
requests/user.http - Click "Send Request" above any request
Or use curl:
curl http://localhost:3000/users
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"john@example.com"}'pnpm approve-builds
# Select: better-sqlite3 (press space, then enter)This is a one-time setup required by pnpm for native dependencies.
pnpm db:generateChange port in .env:
PORT=3001# Check if PostgreSQL is running
pg_isready
# Test connection
psql "postgresql://USER:PASSWORD@HOST:PORT/DATABASE"Common causes: wrong credentials, database doesn't exist, port blocked, SSL required (?sslmode=require).
Q: Can I use this in production? A: Yes! The generated code is production-ready. Use PostgreSQL for production and add authentication, CORS, and rate limiting as needed.
Q: What databases are supported? A: SQLite and PostgreSQL. MySQL and MongoDB support planned.
Q: Can I add a database to a project that was created without one?
A: Yes! Run npx kickpress add db [sqlite|postgresql] from inside your project. It wires up everything automatically.
Q: Can I switch from SQLite to PostgreSQL later?
A: Yes! Update the provider in schema.prisma and DATABASE_URL in .env, then re-run migrations.
Q: What if I don't need a database?
A: Use -d none. The project will have no Prisma dependencies at all, and you can add one later with kickpress add db.
Q: How is this different from NestJS? A: NestJS is a framework. Kickpress is a scaffolding tool that generates plain Express.js code.
Q: Can I customize the generated code? A: Absolutely β it's all yours to modify. The CLI just generates a well-structured starting point.
Q: Can I disable validation on specific endpoints? A: Yes, remove the validation middleware from the route definition.
Contributions are welcome!
- Fork the repository
- Create your feature branch
- Make your changes
- Submit a pull request
Marc Tyson CLEBERT
- Website: marctysonclebert.com
- GitHub: @clebertmarctyson
- Twitter: @ClebertTyson
- Email: contact@marctysonclebert.com
MIT Β© Marc Tyson CLEBERT
- π Report Issues
- π‘ Request Features
- π§ Email: contact@marctysonclebert.com
- β Star on GitHub
- β Buy me a coffee
Made with β and β€οΈ by Marc Tyson CLEBERT