{"id":23428671,"url":"https://github.com/marknjunge/nest-boilerplate","last_synced_at":"2025-04-12T20:20:55.011Z","repository":{"id":37533548,"uuid":"185965775","full_name":"MarkNjunge/nest-boilerplate","owner":"MarkNjunge","description":"A boilerplate for NestJS with support for database, swagger, config, rate limiting, logging, OpenTelemetry, validation, exception handling, docker, testing and CI.","archived":false,"fork":false,"pushed_at":"2025-03-14T14:08:51.000Z","size":6301,"stargazers_count":38,"open_issues_count":0,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-26T14:38:44.627Z","etag":null,"topics":["continuous-integration","docker","end-to-end-testing","knexjs","nestjs","nestjs-boilerplate","nestjs-e2e","nodejs","objectionjs","opentelemetry","opentelemetry-javascript","postgresql","restful-api","swagger","swagger2","testing","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MarkNjunge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-10T10:04:59.000Z","updated_at":"2025-03-21T03:15:11.000Z","dependencies_parsed_at":"2023-01-29T15:45:24.794Z","dependency_job_id":"6cf8e761-ad8b-46f9-af4c-6349905398ae","html_url":"https://github.com/MarkNjunge/nest-boilerplate","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkNjunge%2Fnest-boilerplate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkNjunge%2Fnest-boilerplate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkNjunge%2Fnest-boilerplate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkNjunge%2Fnest-boilerplate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MarkNjunge","download_url":"https://codeload.github.com/MarkNjunge/nest-boilerplate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248626368,"owners_count":21135650,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["continuous-integration","docker","end-to-end-testing","knexjs","nestjs","nestjs-boilerplate","nestjs-e2e","nodejs","objectionjs","opentelemetry","opentelemetry-javascript","postgresql","restful-api","swagger","swagger2","testing","typescript"],"created_at":"2024-12-23T07:14:27.248Z","updated_at":"2025-04-12T20:20:54.997Z","avatar_url":"https://github.com/MarkNjunge.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nest Starter\n\n[![CI](https://github.com/MarkNjunge/nest-boilerplate/workflows/Main%20Workflow/badge.svg)](https://github.com/MarkNjunge/nest-boilerplate/actions/workflows/main-workflow.yml)\n[![Known Vulnerabilities](https://snyk.io/test/github/MarkNjunge/nest-boilerplate/badge.svg)](https://snyk.io/test/github/MarkNjunge/nest-boilerplate)\n\nA boilerplate for [NestJS](https://nestjs.com/), using Fastify.\n\n## Features\n\n- [Config](#config)\n- [Database](#database)\n- [Swagger (API docs)](#swagger)\n- [Query Parsing](#query-parsing)\n- [File Upload](#file-upload)\n- [Logging](#logging)\n- [Request Context](#request-context)\n- [Auth guard](#auth-guard)\n- [Rate limiting](#rate-limiting)\n- [Request body validation](#request-body-validation)\n- [Exception Handling](#errors--exception-handling)\n- [Health Check](#health-check)\n- [OpenTelemetry](#opentelemetry)\n- [Docker](#docker)\n- [Testing](#testing)\n- [Continuous Integration](#ci)\n\n## Installation\n\n```bash\n$ npm install\n```\n\n## Running\n\n```bash\n# development\n$ npm run start\n\n# watch mode\n$ npm run start:dev\n\n# production mode\n$ npm run start:prod\n```\n\n## Config\n\nThe [node-config](https://www.npmjs.com/package/config) package to manage configs.\n\nDefault config values are found in [default.json](./config/default.json).  \nThese values can be overridden by:\n- Creating config files as described in [node-config docs](https://github.com/node-config/node-config/wiki/Configuration-Files)\n- Creating a `local.json` file in _config/_\n- Creating a `.env` file in the project directory. (supported via dotenv)\n- Setting environment variables. See the environment variable mappings\n  in [custom-environment-variables.json](./config/custom-environment-variables.json).\n\n## CORS\n[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) is configurable via the config.\n\n\nThe following values are acceptable for `config.cors.origins`:\n- `*` - Will accept any origin. The `access-control-allow-origin` header will always respond with the exact origin of the request, not a `*`.   \n- `https://example.com,https://example2.com` - Will accept all domains in the comma separated list.\n\n## Database\n\n[Objection](https://vincit.github.io/objection.js/) (which uses [Knex.js](https://knexjs.org/)) \nis used for database operations.\n\nIt uses PostgreSQL by default, but that can be changed by changing the `client`\nin [knexfile.ts](./src/db/knexfile.ts).  \nSee [Knex documentation](https://knexjs.org/guide/#node-js) for supported databases.\n\nConversion of column names from camel case to snake case is automatically done in most cases.   \nIt however does not work in all cases. See excerpt below from Objection docs\n([Snake case to camel case conversion](https://vincit.github.io/objection.js/recipes/snake-case-to-camel-case-conversion.html)):\n\n\u003e When the conversion is done on objection level only database columns of the returned\n\u003e rows (model instances) are convered to camel case. You still need to use snake case in\n\u003e relationMappings and queries. Note that insert, patch, update and their variants still\n\u003e take objects in camel case. The reasoning is that objects passed to those methods\n\u003e usually come from the client that also uses camel case.\n\n\n### Migrations\n\nSchema changes require migrations.\n\nMigrations can be created using:\n```bash\nnpm run migration:make\n```\n\nWhen the server starts, migrations will run automatically, or, you can run the migrations\nusing `npm run migration:latest`\n\n## Swagger\n\nSwagger documentation is automatically generated from the routes.\n\nBy default it is available at http://127.0.0.1:3000/docs\n\n## Query Parsing\n\nURL query to DB query parsing is available. See [query-parser.ts](./src/utils/query-parser.ts).\n\n**Note:** There is currently no limitation put on the complexity of the query, so this should be exposed with caution. \n\nExample:\n```\nlimit=10\u0026page=1\u0026orderBy=(averageRating,DESC)\u0026filter=(authors,=,Bill Bryson):(yearPublished,\u003e,2000)\n```\n\n```SQL\nSELECT \"*\"\nFROM   \"...\"\nWHERE  \"authors\" = 'Bill Bryson'\n  AND \"year_published\" \u003e '2000'\nORDER  BY \"average_rating\" DESC\n  LIMIT  10 \n```\n\n### Paging\n\nPaging can be done using `limit` and `page`.\n\n### Filters\n\nFilters take the format of `(column,operand,value)` where the operand can be `=,\u003e,\u003e=,\u003c,\u003c=`.  \nColumn names are automatically converted to snake case.  \nMultiple filters can be specified using a colon `:` as the delimiter.\n\n### Ordering\n\nOrdering takes the format of `(column,direction)` where direction can be `desc,DESC,asc,ASC`.  \nMultiple orderings can be specified using a colon `:` as the delimiter.\n\n## File Upload\n\nFile uploads are configured.\n\nSee [FileUploadDto](./src/models/file-upload/file-upload.dto.ts) for an example dto.\n\n### Config\n\n`maxSize: number` - Max size in bytes. Default 5MB.  \n`uploadDir: string` - Upload directory. If blank, it will default to the OS's temp directory.  \n`removeAfterUpload: boolean` - Whether to delete files after the request completes.\n\n## Logging\n\n[Winston](https://www.npmjs.com/package/winston) is used as a logging library.\n\nCreate a logger instance using `new Logger()`.  \nA parameter can be passed into the constructor and to be used as a tag (defaults to \"Application\").\n\nFor example,\n\n```Typescript\nimport { Logger } from \"@/logging/Logger\";\n\nconst logger = new Logger(\"AppService\");\n```\n\n```typescript\nthis.logger.debug(\"Hello!\");\n\n// 2019-05-10 19:47:21.570 | debug: [AppService] Hello!\n```\n\nA custom tag can be passed into the log functions.\n\n```Typescript\nthis.logger.debug(\"Hello!\", { tag: \"AppService.getHello\" });\n\n// 2019-05-10 19:54:43.062 | debug: [AppService.getHello] Hello!\n```\n\nExtra data can be passed into the log functions. To enable printing it to the console, set the\nconfig `logging.logDataConsole` to `true`.\n\n```Typescript\nthis.logger.debug(`Hello ${ctx.traceId}`, { data: { traceId: ctx.traceId } });\n\n// 2025-01-15 15:11:57.090 | debug: [AppService.getHello] Hello 47e4a86ea7c0676916b45bed6c80d1bb\n// {\n//   \"traceId\": \"47e4a86ea7c0676916b45bed6c80d1bb\"\n// }\n```\n\n### Custom Transports\n\nSee [SampleTransport](src/logging/Sample.transport.ts) for an example.\n\n### Redact Private Keys\n\nPrivate keys are automatically redacted in logged data.\n\nThe private keys are specified in [redact.ts](src/utils/redact.ts)\n\n```json\n{\n  \"username\": \"mark\",\n  \"contact\": {\n    \"email\": \"REDACTED\"\n  }\n}\n```\n\n## Request Context\n\n[nestjs-cls](https://papooch.github.io/nestjs-cls/) is implemented to maintain the request state.\n\nThe request ID is the trace ID if observability is enabled, otherwise it's a random string.\n\nIt can be accessed using the `AppClsService`.\n\n```typescript\nclass Service {\n  constructor(\n    // This needs to be ClsService\u003cAppClsStore\u003e. AppClsService will not work.\n    private readonly clsService: ClsService\u003cAppClsStore\u003e\n  ) {}\n  \n  handler() {\n    this.clsService.getId();\n  }\n}\n```\n\nAlternatively, the `@ReqCtx()` can be used.\n\n```typescript\nclass Controller {\n  @Get()\n  getHello(@ReqCtx() ctx: IReqCtx) {\n    console.log(ctx.traceId) // 0d8df9931b05fbcd2262bc696a1410a6\n  }\n}\n```\n\n## Authentication\n\nA simple authentication scheme is implemented in [auth.guard.ts](./src/guards/auth.guard.ts)\n\n\n## Rate Limiting\n\nA rate limiter is configured\nusing [@nestjs/throttler](https://github.com/nestjs/throttler).  \nIt defaults to 100 request per minute (configurable in [default.json](./config/default.json))\n.\n\n## Request Body Validation\n\n[class-validator](https://www.npmjs.com/package/class-validator)\nand [class-transformer](https://www.npmjs.com/package/class-transformer) are used to validate\nrequest bodies.\n\nSee [class-validator decorators](https://www.npmjs.com/package/class-validator#validation-decorators)\nfor available validation options.\n\nAn example of a response to an invalid body:\n\n```JSON\n{\n  \"status\": 400,\n  \"message\": \"Validation errors with properties [name,username,contact.mail,contact.email]\",\n  \"code\": \"ValidationError\",\n  \"meta\": [\n    {\n      \"property\": \"name\",\n      \"constraints\": [\n        \"property name should not exist\"\n      ]\n    },\n    {\n      \"property\": \"username\",\n      \"constraints\": [\n        \"username should not be empty\"\n      ]\n    },\n    {\n      \"property\": \"contact.mail\",\n      \"constraints\": [\n        \"property mail should not exist\"\n      ]\n    },\n    {\n      \"property\": \"contact.email\",\n      \"constraints\": [\n        \"email should not be empty\"\n      ]\n    }\n  ]\n}\n```\n\nNB: Raising an error when unknown values are passed can be disabled by\nsetting `validator.forbidUnknown` to `false` in the config.\n\n## Response Mapping\n\nCleaning response objects using can be enabled using the `@Serialize(ClassName)` decorator. \nIt uses [class-transformer](https://www.npmjs.com/package/class-transformer). \n\n## Errors \u0026 Exception Handling\n\nExceptions should be thrown using the **custom** [HttpException](src/utils/HttpException.ts) class.\n\n```typescript\nthrow new HttpException(404, `User ${1} was not found`, ErrorCodes.INVALID_USER, { id: 1 });\n```\n\n```json\n{\n  \"status\": 404,\n  \"message\": \"User 1 was not found\",\n  \"code\": \"InvalidUser\",\n  \"traceId\": \"775523bae019485d\",\n  \"meta\": {\n    \"id\": 1\n  }\n}\n```\n\nRegular errors and unhandled exceptions are also caught and returned as a 500 response.\n\n```json\n{\n  \"status\": 500,\n  \"message\": \"Uncaught exception\",\n  \"code\": \"InternalError\",\n  \"traceId\": \"d3cb1b2b3388e3b1\"\n}\n```\n\n# Health Check\n\nHealth check endpoints are set up at `/ready` and `/live`.\n\n## OpenTelemetry\n\n[OpenTelemetry](https://opentelemetry.io/docs/languages/js/) support in included with support for traces, metrics and logs.\n\n[@opentelemetry/auto-instrumentations-node](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) is set up to automatically collect metrics and spans for various services\n\nSee the [observability README](./observability/README.MD) for a compose file with various services for collecting and viewing signals.\n\n**Note:** Global and per signal instrumentation needs to be enabled via [config](#config)\n\n### Traces\n\nAutomatic instrumentation is enabled and will suite most needs. \n\nCustom spans can be created as described in the [OpenTelemetry docs](https://opentelemetry.io/docs/languages/js/instrumentation/#create-spans).\n\n### Metrics\n\nHTTP metrics are automatically collected by `@opentelemetry/instrumentation-http`\n\n```\nsum by(http_route) (rate(nb_http_server_duration_milliseconds_count[1m]))\n```\n\nSee [OpenTelemetry docs](https://opentelemetry.io/docs/languages/js/instrumentation/#metrics) for how to create custom metrics.\n\n```typescript\nconst meter = opentelemetry.metrics.getMeter(\"UserService\");\nconst getUserCounter = this.meter.createCounter(\"get_user\")\ngetUserCounter.add(1, { user_id: id });\n```\n\n### Logs\n\nLogs are sent to the OpenTelemetry Collector using the [OtelTransport](src/logging/otel.transport.ts).\n\nSee [logging](#logging) for how to log.\n\n## Docker\n\nThe application can be run using docker.\n\nBuild\n\n```bash\ndocker build -t nest-boilerplate .\n\ndocker run -p 3000:3000 nest-boilerplate\n```\n\nDocker Compose can be used to start the application and a database.\n\n```\ndocker compose up -d\n```\n\n## Testing\n\n### Unit test\n\nThere exist unit tests for controllers and services.\n\nDependencies are mocked using `jest`.\n\n```bash\nnpm run test\n```\n\n### End-to-end test\n\nA Docker container is created to run end to end tests.\n\nSee [docker-compose-e2e.yml](./docker-compose-e2e.yml)\n\n```bash\n# e2e tests (docker)\nnpm run test:e2e\n\n# e2e test (locally)\nnpm run test:e2e:local\n```\n\n### Load test\n\nLoad tests are written using [Grafana k6](https://grafana.com/oss/k6/).\n\nSee [load-test/](./load-test) directory.\n\n# CI\n\n[Github Actions config](./.github/workflows/main-workflow.yml)  \n![](https://github.com/MarkNjunge/nest-boilerplate/workflows/Main%20Workflow/badge.svg)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarknjunge%2Fnest-boilerplate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarknjunge%2Fnest-boilerplate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarknjunge%2Fnest-boilerplate/lists"}