Getting started
This guide takes you from an empty NestJS project to working, documented CRUD endpoints in a few minutes.
1. Install
npm install @ackplus/nest-crud
# peer dependencies (if not already installed)
npm install @nestjs/common @nestjs/core @nestjs/platform-express \
@nestjs/swagger @nestjs/typeorm typeorm class-validator class-transformer \
reflect-metadata@ackplus/nest-crud declares these as peer dependencies so it uses the same NestJS/TypeORM versions as your app. It supports NestJS 10 and 11.
2. Define an entity
Extend BaseEntity to get a UUID id plus createdAt, updatedAt, and a deletedAt soft-delete column.
import { Entity, Column } from 'typeorm';
import { BaseEntity } from '@ackplus/nest-crud';
@Entity('users')
export class User extends BaseEntity {
@Column({ unique: true })
email: string;
@Column()
firstName: string;
@Column({ default: true })
isActive: boolean;
}The service assumes the primary key
idis provided byBaseEntity. If you need an ordered list, extendBaseEntityWithOrderinstead (adds anordercolumn).
3. Create a service
Extend CrudService<T> and inject the entity repository.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CrudService } from '@ackplus/nest-crud';
import { User } from './user.entity';
@Injectable()
export class UserService extends CrudService<User> {
constructor(
@InjectRepository(User)
public repository: Repository<User>,
) {
super(repository);
}
}4. Create a controller
Apply @Crud(). It already applies @Controller(path) for you — do not add a second @Controller(). The controller must expose the service as a property named service.
import { ApiTags } from '@nestjs/swagger';
import { Crud } from '@ackplus/nest-crud';
import { User } from './user.entity';
import { UserService } from './user.service';
@ApiTags('users')
@Crud({
entity: User,
path: 'users',
routes: {
findMany: { enabled: true },
findOne: { enabled: true },
create: { enabled: true },
update: { enabled: true },
delete: { enabled: true },
},
})
export class UserController {
constructor(public service: UserService) {}
}⚠️ Routes use the object form
{ enabled: true }. The shorthandfindMany: truealso works. Routes you don't list are enabled by default — to expose only a subset, disable the rest with{ enabled: false }.
5. Wire up the module
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}And a root module with a database connection:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserModule } from './user.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres', // or mysql, sqlite, ...
url: process.env.DATABASE_URL,
entities: [User],
synchronize: true, // dev only
}),
UserModule,
],
})
export class AppModule {}6. Enable Swagger (optional but recommended)
// main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder().setTitle('My API').setVersion('1.0').build();
SwaggerModule.setup('api', app, SwaggerModule.createDocument(app, config));
await app.listen(3000);
}
bootstrap();Open http://localhost:3000/api — every generated route is documented, including filter operators, relations, pagination, and request/response schemas.
7. Make your first requests
# Create
curl -X POST localhost:3000/users -H 'content-type: application/json' \
-d '{"email":"jane@example.com","firstName":"Jane"}'
# List — returns { items, total }
curl 'localhost:3000/users'
# Get one
curl localhost:3000/users/<id>
# Update (PUT, not PATCH)
curl -X PUT localhost:3000/users/<id> -H 'content-type: application/json' \
-d '{"firstName":"Janet"}'
# Delete
curl -X DELETE localhost:3000/users/<id>Things to know up front
GET /resourceisfindManyand returns{ items, total }.GET /resource/get/allisfindAlland returns a bare arrayT[].- Update is
PUT /:id, notPATCH. - Soft-delete routes (
/:id/restore,/:id/trash,/restore/bulk,/trash/bulk) only exist when you passsoftDelete: trueto@Crud(). - List queries are capped by
maxPerPage(default 5000). - The query layer ships 29 filter operators, relations, multi-sort, and aggregates (
count/sum/avg/min/maxover relations withhaving) — see Querying. - Lock down sensitive columns with
@CrudHidden()so they can never be selected, filtered, or sorted by clients.
Talking to your API from a client
You don't have to build the JSON query strings by hand. The query builders produce the exact wire format the server expects, on any platform:
- JS / TS (React, Angular, Vue, Node):
@ackplus/nest-crud-request - Flutter / Dart:
nest_crud_request
See Packages & links for all three, and the framework guides below.
Next
- Querying — filters, relations, pagination, sorting, aggregates, counts.
- Configuration — every
@Crud()option. - Lifecycle hooks —
beforeCreate,beforeFindMany, … - Auth & guards — protect routes and hide sensitive columns.
- Packages & links — the client query builders (JS + Flutter/Dart).
- Client guides: React · Angular · Vue · Flutter / Dart.
- Error handling · Troubleshooting.