@tanisic/nest-japi v0.0.5
nest-japi
nest-japi is a plugin for NestJS (Express platform only) that lets you build fully compliant JSON:API v1.1 services with minimal boilerplate. It combines powerful technologies like Zod, MikroORM, ts-japi, and @anatine/zod-openapi to create elegant and maintainable APIs.
β¨ Features
- βοΈ Plug-and-play NestJS integration (requires Express platform)
- π JSON:API compliant controllers, routes, payloads, and relationships
- π Zod-based schema validation with full TypeScript support
- π Zod β OpenAPI generation via
anatine/zod-openapi
- π¦ JSON:API serialization using
ts-japi
- π Automatic controller and route generation for all standard JSON:API endpoints
- 𧬠Attribute and Relation decorators directly map class properties to JSON:API
attributes
andrelationships
- ποΈ MikroORM required as the data persistence layer
- π Swagger UI support with auto-generated schemas
β οΈ Requirements
- NestJS (Express platform only)
- MikroORM (required)
- Zod
- Each JSON:API resource must define:
- A MikroORM Entity
- A corresponding
@Schema()
class
π¦ Installation
npm install @tanisic/nest-japi zod
π§ Concepts
Every JSON:API resource must have:
β A MikroORM entity
β A schema class using @Schema decorator
If a type is referenced (e.g. as a relationship), its resource must be defined as well.
π Schema Definition
Use @Attribute()
and @Relation()
to define how fields and relationships appear in your API.
Example schema
import { Schema, Attribute, Relation, BaseSchema } from 'nest-japi';
import { z } from 'zod';
import { Post } from '../entities/post.entity';
import { CommentSchema } from './comment.schema';
import { UserSchema } from './user.schema';
@Schema({ jsonapiType: 'post', entity: Post })
export class PostSchema extends BaseSchema<Post> {
@Attribute({ validate: z.number() })
id: number;
@Relation({ schema: () => CommentSchema, many: true, required: false })
comments: CommentSchema[];
@Attribute({ validate: z.date().optional() })
createdAt: Date;
@Attribute({ validate: z.date().optional() })
updatedAt: Date;
@Attribute({ validate: z.string() })
title: string;
@Attribute({ validate: z.string() })
content: string;
@Relation({ schema: () => UserSchema, required: true })
author: UserSchema;
}
π§ How It Works
@Schema()
binds the resource to its MikroORM entity and JSON:API type
@Attribute()
fields are validated with Zod and exposed as JSON:API attributes
@Relation()
defines JSON:API relationships
Validation schemas also generate OpenAPI docs via @anatine/zod-openapi
βοΈ Setup
To set up the JsonApiModule in your NestJS application, you need to use the following configuration:
JsonApiModule.forRoot()
- This method should be called in the AppModule to globally configure the module.JsonApiModule.forFeature()
- This method is used in your feature modules (e.g., PostModule, UserModule, etc.) to register specific features that should be available to that module.
Hereβs how you can integrate both into your application.
- Setting up
JsonApiModule.forRoot()
in AppModule In your AppModule, you should useJsonApiModule.forRoot()
to configure the global settings for nest-japi.
// app.module.ts
import { Module } from '@nestjs/common';
import { JsonApiModule } from 'nest-japi';
import { PostModule } from './post/post.module';
import { UserModule } from './user/user.module';
@Module({
imports: [
JsonApiModule.forRoot({
...
}),
PostModule, // Example of a feature module
UserModule, // Another example of a feature module
],
})
export class AppModule {}
- Setting up
JsonApiModule.forFeature()
in Feature Modules In each feature module, youβll useJsonApiModule.forFeature()
to register specific JSON:API resource.
For instance, in the PostModule, you will register your post-related features:
// post.module.ts
import { Module } from '@nestjs/common';
import { JsonApiModule } from 'nest-japi';
import { PostResource } from './post.controller';
@Module({
imports: [
JsonApiModule.forFeature({
resource: PostResource, // Register the PostResource
}),
],
controllers: [PostController],
})
export class PostModule {}
Here, PostResource will be a controller responsible for handling JSON:API routes.
- Example PostController using JSON:API Resource Controller You can create a controller like this in your PostController:
// post.controller.ts
import { Resource } from 'nest-japi';
import { BaseResource } from 'src/resource/BaseResource';
import { CreatePostSchema, PostSchema, UpdatePostSchema } from 'src/posts/posts.schema';
@Resource({
schemas: {
schema: PostSchema,
createSchema: CreatePostSchema,
updateSchema: UpdatePostSchema,
},
disabledMethods: ["getOne"] // Disable getOne method
path: 'v1/posts',
})
export class PostResource extends BaseResource<
string,
PostSchema,
CreatePostSchema,
PatchPostSchema
> {
override getAll(
query: QueryParams,
request: Request,
): Promise<Partial<DataDocument<any>>> {
return super.getAll(query, request);
}
@ApiOperation({
description: 'test123', // Write own OpenAPI docs
})
override patchOne(id: string, body: PatchBody<PatchPostSchema>) {
return super.patchOne(id, body);
}
}
- Example
PostSchema
import { Schema, Attribute, Relation, BaseSchema } from 'nest-japi';
import { z } from 'zod';
import { Post } from '../entities/post.entity';
import { CommentSchema } from './comment.schema';
import { UserSchema } from './user.schema';
@Schema({ jsonapiType: 'post', entity: Post }) // Bind JSON:API type 'post' and MikroORM entity Post
export class PostSchema extends BaseSchema<Post> {
@Attribute({ validate: z.number() })
id: number;
@Relation({ schema: () => CommentSchema, many: true, required: false })
comments: CommentSchema[];
@Attribute({ validate: z.date().optional() })
createdAt: Date;
@Attribute({ validate: z.date().optional() })
updatedAt: Date;
@Attribute({ validate: z.string() })
title: string;
@Attribute({ validate: z.string() })
content: string;
@Relation({ schema: () => UserSchema, required: true })
author: User;
}
β οΈ Required steps for NestJS 11+
π§ Why is this needed?
In Express v5, query parameters are no longer parsed using the qs
library by default.
Instead, Express uses a simple parser that does not support nested objects or arrays β which breaks compatibility with current query parse implementation.
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.set('query parser', 'extended'); // β
Required
await app.listen(3000);