DEV Community

Bibek Ghimire
Bibek Ghimire

Posted on

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

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

Fully customizable

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>("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.2.0 — the package is functional and published but there's a lot more planned:

  • Support for other ORMs (Sequelize, TypeORM)
  • More advanced filtering options
  • 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 (1)

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!