Skip to content

tjbroodryk/nestjs-rest-contracts

Repository files navigation

NestJS Type Safe REST Contracts

Type Safe, contract-driven REST Controllers for NestJS applications.

Uses zod object defintions and ts-rest to create typesafe REST endpoint contracts.

Note This library is simply to force NestJS controllers to implement a ts-rest contract. The contracts and client lib themselves are still standard ts-rest.

Features

  • Type-Safe Controllers - Controllers are forced to implement the interface defined by the contract
  • Automatic request body, path param, and query parsing using the zod definitions defined in the contract.
  • Automatic parameter unmarshelling - no need for additional param decorators like Body or ApiDecorator. Its already handled.
  • Automatic binding of methods to endpoints - no need for additional decorators like Post or Api. Its already handled.
  • Utilities for creating response objects like NoContent, Ok etc that provide the correct status codes.
  • Combine contracts as per ts-rest.

Installation

npm i @ts-rest/core nestjs-rest-contracts

TODO

  • [] Ensure all entire contract has been implemented
  • [] ContractModule - could be used for things like automatically creating a swagger doc + route if enabled. E.g.
@Module({
  imports: [ContractModule.forRoot({
    openApi: {
      enabled: true
    }
  })]
})
export class AppModule {

}

Example

  1. Create contract
import { initContract } from '@ts-rest/core';
import { z } from 'zod';

export const c = initContract();

export const stackContract = c.router({
  update: {
    method: 'POST',
    path: '/stacks/:name/update',
    responses: {
      204: c.response<void>(),
      409: c.response<{ message: string }>(),
    },
    body: z.object({
      foo: z.string(),
    }),
    pathParams: {
      name: z.string(),
    },
    summary: 'Run update for stack',
  },
});
  1. Create your Controller
import { stackContract } from './contract';
import {
  ContractController,
  ArgsShape,
  ResponseShape,
  NoContent,
  Conflict,
} from 'nestjs-rest-contracts';

type Contract = typeof stackContract;
type Args<T extends keyof Contract> = ArgsShape<Contract[T]>;
type Response<T extends keyof Contract> = ResponseShape<Contract[T]>;

@ContractController(stackContract)
export class StackController {
  async update(args: Args<'update'>): Response<'update'> {
    if (args.params.name === 'foo') {
      return NoContent();
    }
    return Conflict('some message');
  }
}
  1. Register your Controller
import { Module } from '@nestjs/common';
import { StackController } from './stack.controller';

@Module({
  controllers: [StackController],
})
export class AppModule {}
  1. Profit