I've written the same NestJS CRUD code at least 50 times. Controller with five endpoints. Service that calls a repository. Repository that talks to the database. Create DTO, Update DTO. Module that wires everything together. Every. Single. Resource.
It's not hard work. It's just tedious work. And tedious work is where bugs hide - a misspelled field name, a missing @Injectable(), forgetting to add the module to app.module.ts.
So I built zapit, a CLI that generates all of it from a Zod schema file. One command, full CRUD layer.
The idea
If you're already using Zod (and if you're writing TypeScript APIs in 2025+, you probably should be), your schema is your source of truth. It defines your fields, types, constraints, and defaults. Why write that information a second time in DTOs and entity classes?
zapit reads your Zod schema and generates everything downstream from it.
Quick start
No install required:
npx @tysoncung/zapit generate schemas/product.schema.ts
Or if you're starting from scratch:
npx @tysoncung/zapit init my-api
cd my-api
This scaffolds a full NestJS project with DynamoDB config, Zod validation pipe, and a sensible folder structure.
Define your schema
Here's a typical schema file:
// schemas/product.schema.ts
import { z } from 'zod';
export const ProductSchema = z.object({
name: z.string().min(1).max(255),
price: z.number().positive(),
description: z.string().optional(),
category: z.enum(['electronics', 'clothing', 'food', 'other']),
inStock: z.boolean().default(true),
tags: z.array(z.string()).optional(),
});
Nothing special - just Zod as you'd normally write it.
What gets generated
Run npx @tysoncung/zapit generate schemas/product.schema.ts and you get six files:
Controller
// src/product/product.controller.ts
@Controller('products')
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Post()
create(@Body() dto: CreateProductDto) { ... }
@Get()
findAll() { ... }
@Get(':id')
findOne(@Param('id') id: string) { ... }
@Put(':id')
update(@Param('id') id: string, @Body() dto: UpdateProductDto) { ... }
@Delete(':id')
remove(@Param('id') id: string) { ... }
}
Service
Standard service layer that delegates to the repository. Clean separation of concerns - you add your business logic here.
Repository
DynamoDB repository out of the box with create, findAll, findOne, update, and remove methods.
DTOs
CreateProductDto and UpdateProductDto derived from your Zod schema. The update DTO makes all fields optional (partial update pattern). Validation is handled by a Zod validation pipe, so your runtime validation matches your types exactly.
Module
// src/product/product.module.ts
@Module({
controllers: [ProductController],
providers: [ProductService, ProductRepository],
exports: [ProductService],
})
export class ProductModule {}
Wired up and ready to import into your AppModule.
Why Zod as the source of truth?
A few reasons:
- You're probably already using it. Zod is the most popular runtime validation library in the TypeScript ecosystem.
- Single source of truth. Define fields, types, and constraints once. No drift between your validation layer and your DTOs.
- It's just TypeScript. No config files, no YAML, no custom DSL. Your schema is code you can test, compose, and version control like everything else.
Free vs Pro
The free tier is fully functional:
- ✅ Full CRUD generation (controller, service, repository, DTOs, module)
- ✅ DynamoDB repository
- ✅ NestJS module auto-wiring
- ✅ Zod validation pipe
- ✅
zapit initproject scaffolding
If you need more, there's a Pro tier that includes:
- Prisma support (swap DynamoDB for any Prisma-supported database)
- Auth middleware generation
- Pagination helpers
- Audit logging
- Multi-tenant support
- OpenAPI spec generation
- AWS CDK deployment scaffolds
Pro is there for teams that want to move fast on production-grade APIs, but the free version covers the core use case: stop writing boilerplate.
What this isn't
zapit isn't a framework. It doesn't add runtime dependencies or lock you into a pattern. It generates standard NestJS code that you own and modify however you want. If you outgrow it, you just... edit the files. No vendor lock-in, no magic.
What's next
I'm actively working on:
- More database adapters (MongoDB, PostgreSQL repos without Prisma)
- Relationship handling between schemas
- Custom template support
- Plugin system for community generators
Try it
npx @tysoncung/zapit generate your-schema.ts
I'd love to hear what you think - what works, what's missing, what would make this useful for your projects. Drop an issue on GitHub or hit me up in the comments.
Top comments (0)