DEV Community

Bibek Ghimire
Bibek Ghimire

Posted on

I built an npm package that auto-generates a REST API from your Prisma schema

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"] },
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

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
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

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",
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
brighto7700 profile image
Bright Emmanuel

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!

Collapse
 
bibek_ghimire_aa80c2f89f9 profile image
Bibek Ghimire

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!

Collapse
 
bibek_ghimire_aa80c2f89f9 profile image
Bibek Ghimire

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.

Collapse
 
brighto7700 profile image
Bright Emmanuel

Absolute legend. 🚀 I didn't expect an update this quickly! RBAC plus rate limiting completely solves the biggest hurdles for generating these routes.