A NestJS API template to be used to create small single-purpose services
This is a template for an API connected to a database. Data is stored in a Postgres database and served over HTTPS to the requester. Most services are part of a NestJS application. Prisma is the application's ORM. OpenAPI Swagger documentation is automatically generated by the server at http://localhost:port env variable >/api/ in local development environments, or in any other environment by adding /api to the api URL. This can be helpful to get a more visual overview of all available endpoints.
The following commands are for macOS / Linux, but you can find equivalent instructions for Windows machines online.
Configuration of the API is pulled from environment variables defined in an .env file in the api directory. Copy the .env.template file in api into a .env file. Some keys are secret and are internally available. The template file includes default values and descriptions of each variable.
If you don't have yarn installed, you can install homebrew with these instructions and then do so with brew install yarn.
We are currently using Node version 20. You can install Node using homebrew with the following command: brew install node@20.
If you have multiple versions of Node installed, you can use nvm (node version manager), brew, or other similar tools, to switch between them. Ensure you're on the right version by checking with node -v.
If along the way you get env: node: No such file or directory, inspect the output from installing node for instructions on if you might need to add node to certain terminal paths.
You can install Postgres using homebrew with the following command: brew install postgresql@15. You then start it with brew services start postgresql@15.
Install project dependencies with yarn install from within the api directory.
The following command will generate and build the Prisma schema and setup the database with seeded data: yarn setup.
If this is your first time running this command and you see psql: error: FATAL: database "<username>" does not exist you may need to run createdb <username> first.
You will also need to update the DATABASE_URL environment variable to include your username.
If you're using VSCode, you can install the Postgres explorer extension to inspect your local database. When you click on the + to create a new connection, you can use the following inputs to each question to create a connection to the newly created database: localhost, <username>, hit enter for password, 5432, standard, the name of your database, and a descriptive name like local-api. Once the connection is established, you can inspect the database.
To start the application run: yarn dev.
If you're using VSCode, you can install the Prisma extension to add syntax highlighting and formatting to Prisma schema files.
To modify the Prisma schema you will need to work with the schema.prisma file. This file controls the following:
- The Structure of each model
- The Relationships between models
- Enum creation for use in both the API and the database
- How Prisma connects to the database
You will need to:
- Add the field in the DTO
- Run
yarn generate:clientto add the type to the swagger file - Manually add the field to
schema.prisma - Run
yarn prisma migrate dev --name <name of migration>to create the migration file
We use the following conventions:
- model and enum names are capitalized camel case (e.g. HelloWorld)
- model and enum names are @@map()ed to lowercase snake case (e.g. hello_world)
- model names are always plural (e.g. `Applicants` for the model and `applicants` for the @@map()ed version)
- a model's fields are lowercase camel case (e.g. helloWorld)
- a model's fields are @map()ed to lowercase snake case (e.g. hello_world)
Endpoints live in controllers under src/controllers. They follow the NestJs standards
Controllers are given the extension .controller.ts and the model name (listing, application, etc) is singular. So for example listing.controller.ts.
The exported class should be in capitalized camel case (e.g. ListingController).
DTOs (Data Transfer Objects) are how we flag what fields endpoints will take in, and what the form of the response from the api will be.
We use the packages class-transformer & class-validator for this.
DTOs are stored under src/dtos, and are broken up by what model they are related to. There are also shared DTOs which are stored under the shared sub-directory.
DTOs are given the extension .dto.ts and the file name is lowercase kebab case (e.g. listings-filter-params.dto.ts).
The exported class should be in capitalized camel case (e.g. ListingFilterParams) and does not include the DTO as a suffix.
These are enums used by NestJs primarily for either taking in a request or sending out a response. Database enums (enums from Prisma) are part of the Prisma schema and are not housed here.
They are housed under src/enums and the file name is lowercase kebab case and end with -enum.ts.
So for example filter-key-enum.ts.
The exported enum should be in capitalized camel case (e.g. ListingFilterKeys).
Modules connect the controllers to services and follow NestJS standards.
Modules are housed under src/modules and are given the extension .module.ts. The model name (listing, application, etc) is singular. So for example listing.module.ts.
The exported class should be in capitalized camel case (e.g. ListingModule).
Services are where business logic is performed as well as interfacing with the database.
Controllers should be calling functions in services in order to do their work.
They follow the NestJS standards.
Services are housed under src/services and are given the extension .services.ts. The model name (listing, application, etc) is singular. So for example listing.service.ts.
The exported class should be in capitalized camel case (e.g. ListingService).
We use 2 guards: ThrottleGuard and ApiKeyGuard.
ThrottleGuard acts as a rate limiter for the API. Its done on an IP basis.
ApiKeyGuard is something that was built to prevent access to the API from sources without the secret pass key. It requires that secret pass key to be passed in the request header. Any request that does not have that info while the API_PASS_KEY env variable is set will be rejected.
There are 2 different kinds of tests that the api supports: Integration tests and Unit tests.
Integration Tests are tests that DO interface with the database, reading/writing/updating/deleting data from that database.
Unit Tests are tests that MOCK interaction with the database, or test functionality directly that does not interact with the database.
Integration Tests are housed under test/integration, and files are given the extension .e2e-spec.ts.
These tests will generally test going through the controller's endpoints and will mock as little as possible. Since the order of tests executing can't be guaranteed, tests should be written with the possibility of other tests having run first in mind. One cannot assume that the database will be empty prior to the test running.
Running the following will run all integration tests yarn test:e2e.
Unit Tests are housed under test/unit, and files are given the extension .spec.ts.
These tests will generally test the functions of a service, or helper functions. These tests will mock Prisma and therefore will not interface directly with the database. This allows for verifying the correct business logic is performed without having to set up the database.
Running the following will run all unit tests: yarn test
We have set up both code coverage and code coverage benchmarks. These benchmarks must be met for your PR to pass CI checks. Test coverage is calculated against both the integration and unit test runs. You can run test coverage with the following: yarn test:cov