Skip to content

About

decorator based, AOP, CRUD generator

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

@faster-crud

End-to-end type-safe CRUD for any framework

npm npm downloads CI License TypeScript

Define your entity once with decorators — get a full REST API, validation, filtering, pagination, and auto-generated frontend UI across any backend and frontend framework.

Packages

Package Version Description
@faster-crud/core npm Framework-agnostic decorators & types
@faster-crud/nest npm NestJS integration
@faster-crud/typeorm npm TypeORM adapter
@faster-crud/prisma npm Prisma adapter
@faster-crud/drizzle npm Drizzle adapter
@faster-crud/mongoose npm Mongoose/MongoDB adapter
@faster-crud/mikro-orm npm MikroORM adapter
@faster-crud/hono npm Hono framework adapter
@faster-crud/express npm Express adapter
@faster-crud/fastify npm Fastify adapter
@faster-crud/react npm React hooks & components
@faster-crud/svelte npm Svelte 5 stores & components
@faster-crud/solid npm SolidJS stores & components
@faster-crud/trpc npm tRPC router builder
@faster-crud/graphql npm GraphQL resolver factory
@faster-crud/gen npm CLI code generator
@faster-crud/validation npm Validation middleware
@faster-crud/auth npm JWT auth helpers

Quick Start (NestJS + TypeORM)

npm install @faster-crud/core @faster-crud/nest @faster-crud/typeorm typeorm @nestjs/typeorm reflect-metadata

1. Define your entity

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { Resource, Col, Rule, Deny } from '@faster-crud/core';

@Entity()
@Resource('users', { pagination: { max: 50 } })
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  @Col({ label: 'Username' })
  @Rule.required()
  @Rule.length(3, 20)
  username: string;

  @Column()
  @Col({ label: 'Email' })
  @Rule.email()
  email: string;

  @Column()
  @Col({ label: 'Role' })
  @Deny('create')
  role: string;
}

2. Create a service

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { TypeOrmResourceService } from '@faster-crud/typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService extends TypeOrmResourceService(User) {
  constructor(@InjectRepository(User) repo: Repository<User>) {
    super(repo);
  }
}

3. Wire the module

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NestCrudModule } from '@faster-crud/nest';
import { User } from './user.entity';
import { UserService } from './user.service';

@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    NestCrudModule.forFeature([{ resource: User, service: UserService }]),
  ],
})
export class UsersModule {}

This gives you:

Method Path Description
POST /users Create
GET /users List (with pagination & filters)
GET /users/:id Get by ID
PATCH /users/:id Update
DELETE /users/:id Remove

Prisma Quick Start

npm install @faster-crud/core @faster-crud/nest @faster-crud/prisma @prisma/client reflect-metadata
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { Col, Resource, Rule, Searchable } from '@faster-crud/core';
import { PrismaResourceService } from '@faster-crud/prisma';

const prisma = new PrismaClient();

@Resource('users', {
  softDelete: true,
  cache: { ttl: 5_000 },
})
export class User {
  @Col()
  id: number;

  @Searchable()
  @Col({ label: 'Name' })
  @Rule.required()
  name: string;

  @Searchable()
  @Col({ label: 'Email' })
  @Rule.email()
  email: string;

  @Col()
  deletedAt?: Date | null;
}

@Injectable()
export class UsersService extends PrismaResourceService(User, prisma.user) {}

Include related records by passing Prisma include options directly:

await usersService.get(1, { include: { posts: true } });

await usersService.list(
  {
    page: { current: 1, size: 20 },
    sort: { field: 'name', order: 'asc' },
    filters: {
      name: { op: 'like', value: 'ada' },
    },
  },
  { include: { profile: true } },
);

GraphQL Quick Start

npm install @faster-crud/core @faster-crud/graphql @nestjs/graphql graphql reflect-metadata
import { Resolver } from '@nestjs/graphql';
import { GraphqlCrudFactory, TypeOrmAdapter } from '@faster-crud/graphql';
import { User } from './user.entity';
import { CreateUserDto, UpdateUserDto } from './user.dto';

const UserResolverBase = GraphqlCrudFactory.create({
  entity: User,
  adapter: new TypeOrmAdapter(userRepository),
  dto: {
    create: CreateUserDto,
    update: UpdateUserDto,
  },
});

@Resolver(() => User)
export class UserResolver extends UserResolverBase {}

Generated GraphQL operations:

Type Name
Query users(query?)
Query user(id)
Mutation createUser(input)
Mutation updateUser(id, input)
Mutation deleteUser(id)

For Prisma, replace TypeOrmAdapter with new PrismaAdapter(prisma.user).

List queries currently support page, size, sortField, and sortOrder arguments.


Feature Highlights

CLI Code Generator (@faster-crud/gen)

Scaffold a fully-wired CRUD resource in one command:

npx @faster-crud/gen add User --fields "name:string,email:string" --outdir src/resources

This generates:

src/resources/User/
  User.entity.ts    ← TypeORM entity with @Resource, @Col decorators
  User.service.ts   ← Injectable service extending TypeOrmResourceService
  User.module.ts    ← NestJS module with auto-generated controller

Supported field types: string, number, boolean, Date.

Lifecycle Hooks

Override hooks in your service to run logic before/after CRUD operations:

@Injectable()
export class UserService extends TypeOrmResourceService(User) {
  constructor(@InjectRepository(User) repo: Repository<User>) {
    super(repo);
  }

  async onBeforeCreate(dto: Partial<User>): Promise<Partial<User>> {
    dto.createdAt = new Date();
    return dto;
  }

  async onAfterCreate(entity: User): Promise<void> {
    await this.emailService.sendWelcome(entity.email);
  }

  async onBeforeUpdate(id: number, dto: Partial<User>): Promise<Partial<User>> {
    dto.updatedAt = new Date();
    return dto;
  }

  async onAfterUpdate(entity: User): Promise<void> { /* audit log */ }
  async onBeforeRemove(id: number): Promise<void> { /* check references */ }
  async onAfterRemove(id: number): Promise<void> { /* cleanup */ }
}

All hooks have default no-op implementations — override only what you need.

Soft Delete

For TypeORM, soft delete is auto-detected from @DeleteDateColumn():

import { Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn } from 'typeorm';

@Entity()
@Resource('posts')
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  @Col({ label: 'Title' })
  title: string;

  @DeleteDateColumn()
  deletedAt?: Date;  // ← enables soft delete automatically
}

For Prisma and other adapters, enable it explicitly on the resource:

import { Col, Resource } from '@faster-crud/core';

@Resource('posts', { softDelete: true })
export class Post {
  @Col()
  id: number;

  @Col()
  title: string;

  @Col()
  deletedAt?: Date | null;
}

When soft delete is enabled:

  • DELETE /posts/:id sets deletedAt instead of removing the row
  • List/get queries automatically exclude soft-deleted records

Cache

Enable in-memory caching for list() and get() with @Resource(..., { cache }):

import { Col, Resource, Searchable } from '@faster-crud/core';

@Resource('users', {
  cache: {
    ttl: 10_000,
  },
})
export class User {
  @Col()
  id: number;

  @Searchable()
  @Col()
  name: string;
}
const first = await usersService.list({ page: { current: 1, size: 20 } });
const second = await usersService.list({ page: { current: 1, size: 20 } });

// `second` is served from cache until TTL expires.
await usersService.update(1, { name: 'Ada Lovelace' });

// Writes invalidate cached list/get results automatically.
const fresh = await usersService.get(1);

Filter Operators

GET /users?filters[age][op]=gt&filters[age][value]=18
GET /users?filters[name][op]=like&filters[name][value]=john
GET /users?filters[status][op]=in&filters[status][value]=active,pending
Operator Description Example
eq Equals (default) { op: 'eq', value: 5 }
ne Not equals { op: 'ne', value: 'admin' }
gt Greater than { op: 'gt', value: 18 }
gte Greater than or equal { op: 'gte', value: 18 }
lt Less than { op: 'lt', value: 100 }
lte Less than or equal { op: 'lte', value: 50 }
like Substring match { op: 'like', value: 'john' }
in Value in array { op: 'in', value: [1, 2, 3] }
between Range (inclusive) { op: 'between', value: [10, 100] }

Programmatic usage:

const query: PageQuery<User> = {
  filters: {
    age: { op: 'gte', value: 18 },
    name: { op: 'like', value: 'john' },
    status: { op: 'in', value: ['active', 'pending'] },
  },
};

Swagger Auto-Integration

If @nestjs/swagger is installed, @ApiProperty() and @ApiOperation() decorators are applied automatically based on your @Col and @Rule metadata — zero manual Swagger config needed.

npm install @nestjs/swagger

What gets auto-generated:

  • @ApiProperty({ description, required, type }) on every DTO field
  • @ApiTags() from @Resource name
  • @ApiOperation() on each CRUD endpoint

class-validator Auto-Integration

If class-validator is installed, validation decorators are applied automatically from your @Rule metadata:

npm install class-validator class-transformer
@Rule decorator Auto-applied validator
@Rule.required() @IsNotEmpty()
@Rule.email() @IsEmail()
@Rule.length(min, max) @MinLength(min) + @MaxLength(max)
@Rule.pattern(regex) @Matches(regex)
(optional field) @IsOptional()

ResourceMeta Endpoint

Every CRUD resource exposes GET /__crud/meta for frontend introspection. This returns the full ResourceMeta including resource name, available operations, field definitions (labels, types, UI hints, validation rules), and pagination config — enabling frontends to auto-generate forms and tables.


Framework Adapters

Hono

Works on Bun, Deno, Cloudflare Workers, and Node.js.

npm install @faster-crud/hono hono
import { Hono } from 'hono';
import { HonoCrudRouter } from '@faster-crud/hono';
import { User } from './user.entity';

const app = new Hono();
app.route('/users', HonoCrudRouter(User, userService));

export default app;

Express

npm install @faster-crud/express express
import express from 'express';
import { expressCrudRouter } from '@faster-crud/express';
import { User } from './user.entity';

const app = express();
app.use(express.json());
app.use('/api/users', expressCrudRouter(User, userService));

app.listen(3000);

Fastify

npm install @faster-crud/fastify fastify
import Fastify from 'fastify';
import { FastifyCrudPlugin } from '@faster-crud/fastify';
import { User } from './user.entity';

const fastify = Fastify();
await fastify.register(FastifyCrudPlugin(User, userService), { prefix: '/users' });

fastify.listen({ port: 3000 });

All adapters generate the same REST endpoints and accept any service object implementing create, list, get, update, and remove.


Frontend Libraries

All frontend packages fetch ResourceMeta from the backend to auto-generate tables and forms.

Vue 3 (@faster-crud/vue)

npm install @faster-crud/vue
<script setup lang="ts">
import { useCrud, CrudTable, CrudForm } from '@faster-crud/vue';

const crud = useCrud<User>('/api/users');
</script>

<template>
  <CrudTable :crud="crud" @edit="onEdit" />
  <CrudForm :crud="crud" mode="create" />
</template>

React (@faster-crud/react)

npm install @faster-crud/react
import { useCrud, CrudTable, CrudForm } from '@faster-crud/react';

function UsersPage() {
  const crud = useCrud<User>('/api/users');

  return (
    <>
      <CrudTable crud={crud} onEdit={setEditing} />
      <CrudForm crud={crud} mode="create" onDone={() => crud.fetchList()} />
    </>
  );
}

The useCrud hook returns reactive state for data, total, loading, page, filters, sort, and methods for fetchList, create, update, remove.

Svelte 5 (@faster-crud/svelte)

npm install @faster-crud/svelte
<script>
  import { createCrudStore } from '@faster-crud/svelte';
  import CrudTable from '@faster-crud/svelte/src/CrudTable.svelte';
  import CrudForm from '@faster-crud/svelte/src/CrudForm.svelte';

  const store = createCrudStore('/api/users');
</script>

<CrudTable {store} onEdit={handleEdit} />
<CrudForm {store} mode="create" />

Decorator Reference

Class Decorators

Decorator Description
@Resource(name, options?) Marks a class as a CRUD resource. name becomes the route path. Options: operations, pagination, guardTokens.

Property Decorators

Decorator Description
@Col(options?) Marks a field as a CRUD column. Accepts label, ui, and list options.
@Deny(...ops) Deny a field on specific operations ('create', 'update', etc.).
@Readonly() Shorthand for @Deny('create', 'update').
@Hidden(...views) Omit field from responses on specified views ('list', 'get').
@Searchable() Mark field as searchable for query filtering.
@Ignore() Completely exclude field from all CRUD operations.
@AdminOnly(...ops) Mark field as admin-only for specified operations.

Validation Rules

Decorator Description
@Rule.required(msg?) Field is required.
@Rule.length(min, max, msg?) String length constraint.
@Rule.range(min, max, msg?) Numeric range constraint.
@Rule.email(msg?) Must be valid email format.
@Rule.pattern(regex, msg?) Must match regex pattern.

License

MIT

About

decorator based, AOP, CRUD generator

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages