Every time I started a new backend project with Prisma, I found myself writing the same thing over and over.
A controller for users. A controller for posts. Pagination logic. Error handling. The same response shape copy-pasted across every endpoint. It's tedious, repetitive, and honestly — it's not real work. It's just boilerplate.
So I built apiform to solve this once and for all.
What is apiform?
apiform is an npm package that sits on top of your existing Prisma setup and automatically generates a fully structured REST API from your models. No controllers. No route handlers. No boilerplate.
Your Prisma Schema → apiform → Fully structured REST API
Quick demo
Here's all the code you need to get a full API running:
import { PrismaClient } from "@prisma/client";
import { PrismaLibSql } from "@prisma/adapter-libsql";
import { ApiForm } from "apiform";
const adapter = new PrismaLibSql({ url: "file:./prisma/dev.db" });
const prisma = new PrismaClient({ adapter });
const app = new ApiForm(prisma, {
globalPrefix: "/api",
models: {
user: true,
post: true,
},
});
app.start(3000);
That's it. You instantly get these routes for every model:
| Method | Route | Action |
|---|---|---|
| GET | /api/users |
Find all (paginated) |
| GET | /api/users/:id |
Find by ID |
| POST | /api/users |
Create |
| PATCH | /api/users/:id |
Update |
| DELETE | /api/users/:id |
Delete |
Consistent response shape
Every single endpoint returns the same predictable structure:
{
"success": true,
"message": "USERS_RETRIEVED_SUCCESSFULLY",
"data": [],
"meta": {
"total": 100,
"page": 1,
"limit": 10,
"totalPages": 10,
"hasNext": true,
"hasPrev": false
},
"error": null
}
No more inconsistent responses across your API. Every endpoint behaves the same way.
Built-in pagination, search and filtering
All list endpoints support query parameters out of the box:
GET /api/users?page=2&limit=5&searchBy=name&searchValue=john&sortBy=createdAt&sortOrder=desc
No extra code needed.
Soft delete
Add deletedAt DateTime? to your Prisma model and soft delete is automatically enabled. Records are never permanently deleted — just marked with a timestamp.
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
apiform automatically generates three extra routes:
-
DELETE /api/users/:id— soft deletes the record -
GET /api/users/deleted— retrieves all soft deleted records -
PATCH /api/users/:id/restore— restores a soft deleted record
Soft deleted records are automatically excluded from all normal queries.
Nested relations
Include related models in your queries with a simple query parameter:
GET /api/posts?include=author
GET /api/users/1?include=posts,comments
Role-Based Access Control (RBAC)
Protect your routes with built-in RBAC. apiform checks roles from the request and returns 403 FORBIDDEN if the user doesn't have access:
const app = new ApiForm(prisma, {
rbac: {
rolesPath: "user.roles",
globalRoles: ["user"],
},
models: {
user: {
findAll: { roles: ["admin"] },
delete: { roles: ["admin"] },
},
},
});
Per-route roles override global roles. No extra packages needed.
Rate limiting
Protect your API from abuse with built-in rate limiting:
const app = new ApiForm(prisma, {
rateLimit: {
max: 100, // 100 requests
timeWindow: 60, // per 60 seconds
},
models: {
user: {
create: { rateLimit: { max: 10, timeWindow: 60 } }, // stricter on create
},
},
});
Rate limit headers are automatically included in every response.
apiform is convention over configuration — everything works out of the box. But you can customize anything:
const app = new ApiForm(prisma, {
models: {
user: {
delete: { enabled: false },
findAll: { middleware: [authMiddleware] },
prefix: "/members",
},
},
});
Custom routes
Need something apiform doesn't generate? Add your own routes on top:
app.addRoutes((fastify) => {
fastify.get("/api/users/count", async (request, reply) => {
const count = await prisma.user.count();
reply.send({
success: true,
data: { count },
message: "USERS_COUNTED_SUCCESSFULLY",
meta: null,
error: null,
});
});
});
app.start(3000);
TypeScript generics
All operations are fully typed:
import type { User } from "@prisma/client";
const result = await crud.findById("user", 1);
result.data.email // ✅ fully typed
Why Fastify?
I chose Fastify over Express because it's significantly faster, has first-class TypeScript support, and schema-based validation built in. The performance difference is real — especially for high-traffic APIs.
What's next?
This is v0.4.0 — the package is functional and published. Still planned:
- Support for other ORMs (Sequelize, TypeORM)
- Auto-generated OpenAPI/Swagger docs
Try it
npm install apiform
# or
bun add apiform
I'd genuinely love feedback — especially from developers who work with Prisma daily. What would make this actually useful in your projects? What's missing? What would you change?
Top comments (4)
Great package! Working with Prisma daily, the boilerplate fatigue is incredibly real. To answer your question about what would make it useful: an easy, standardized way to handle Role-Based Access Control (RBAC) on specific auto-generated routes would be a game changer. Also, love the zero-boilerplate approach to soft deletes. Starred the repo!
Thanks a lot! Really appreciate the feedback — Prisma boilerplate fatigue was exactly the pain point that pushed me to build this.
RBAC on auto-generated routes is a great suggestion. I’ve been thinking about introducing a simple configuration layer where you can attach role or permission rules directly to generated endpoints without writing extra middleware. Your comment definitely reinforces that direction.
Glad you liked the soft delete approach too — keeping it zero-boilerplate was one of the main goals.
Thanks again for the ⭐ and the thoughtful suggestion!
An update on this. I just published a new version supporting RBAC and also Rate limiting too. Please try it out and send any feedbacks to me if there are any. Thanks.
Absolute legend. 🚀 I didn't expect an update this quickly! RBAC plus rate limiting completely solves the biggest hurdles for generating these routes.