{"id":15587872,"url":"https://github.com/v-checha/nestjs-prisma-mongodb","last_synced_at":"2025-04-11T01:13:06.024Z","repository":{"id":164415799,"uuid":"639861594","full_name":"v-checha/nestjs-prisma-mongodb","owner":"v-checha","description":"Starter for Nest.js, Prisma ODM, MongoDB","archived":false,"fork":false,"pushed_at":"2024-05-10T10:22:03.000Z","size":817,"stargazers_count":99,"open_issues_count":2,"forks_count":24,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-11T01:12:58.543Z","etag":null,"topics":["boilerplate","casl","jwt","mongodb","nestjs","prisma","starter","swagger"],"latest_commit_sha":null,"homepage":"https://www.nodeteam.onix-systems.com","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/v-checha.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-12T11:51:00.000Z","updated_at":"2025-04-03T17:17:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"d0774950-adfe-455a-b17d-8d552a9af20d","html_url":"https://github.com/v-checha/nestjs-prisma-mongodb","commit_stats":null,"previous_names":["nodeteamdev/nestjs-prisma-mongodb"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v-checha%2Fnestjs-prisma-mongodb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v-checha%2Fnestjs-prisma-mongodb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v-checha%2Fnestjs-prisma-mongodb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v-checha%2Fnestjs-prisma-mongodb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/v-checha","download_url":"https://codeload.github.com/v-checha/nestjs-prisma-mongodb/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248322571,"owners_count":21084337,"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":["boilerplate","casl","jwt","mongodb","nestjs","prisma","starter","swagger"],"created_at":"2024-10-02T22:20:18.017Z","updated_at":"2025-04-11T01:13:06.010Z","avatar_url":"https://github.com/v-checha.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# General\nThis is starter of a Nest.js 10 application with a MongoDB replica set + Prisma ODM.\n\n# Features\n- JWT Authentication\n- CASL Integration\n- Simple query builder\n- Data Pagination\n- Data Sorting\n- Data Filtering\n- Exception Filters\n- Validation Pipes\n- Swagger Documentation\n- Docker Compose\n- MongoDB Replica Set\n- Serializers\n- Health Check\n- SWC (Speedy Web Compiler)\n\n# Providers implemented\n- Prisma\n- Twilio\n- AWS S3\n- AWS SQS\n\n# Requirements\n- Nest.js 10\n- Docker\n- Docker Compose\n- MongoDB\n- Node.js\n- NPM\n\n# Development\n\n## MongoDB Replica Set\n1. Create volume for each MongoDB node\n```bash\ndocker volume create --name mongodb_repl_data1 -d local\ndocker volume create --name mongodb_repl_data2 -d local\ndocker volume create --name mongodb_repl_data3 -d local\n```\n\n2. Start the Docker containers using docker-compose\n```bash\ndocker-compose up -d\n```\n\n3. Start an interactive MongoDb shell session on the primary node\n```bash\ndocker exec -it mongo0 mongosh --port 30000\n\n# in the shell\nconfig={\"_id\":\"rs0\",\"members\":[{\"_id\":0,\"host\":\"mongo0:30000\"},{\"_id\":1,\"host\":\"mongo1:30001\"},{\"_id\":2,\"host\":\"mongo2:30002\"}]}\nrs.initiate(config);\n```\n\n4 Update hosts file\n```bash\nsudo nano /etc/hosts\n\n# write in the file\n127.0.0.1 mongo0 mongo1 mongo2\n```\n\n5. Connect to MongoDB and check the status of the replica set\n```\nmongosh \"mongodb://localhost:30000,localhost:30001,localhost:30002/?replicaSet=rs0\"\n```\n\n## Migration\n\n1. Run migrations\n\n```bash\nnpm run db:migrate:up\n```\n\u003e Need to apply migration `token-ttl-indexes` to database\nThis migration create TTL indexes for `refreshToken` and `accessToken` fields in `TokenWhiteList` model.\nToken will automatically deleted from database when token expriration date will come.\n\n\n## Start\n1. Install dependencies\n\n```\nnpm install\n```\n\n2. Generate Prisma Types\n    \n```\nnpm run db:generate\n```\n\n3. Push MongoDB Schema \n\n```\nnpm run db:push\n```\n\n\n4. Start the application\n\n```\nnpm run start:dev\n```\n\n## SWC\nBy default [SWC](https://swc.rs/) is used for TypeScript compilation, but it can be changed. To use `tsc` as project builder, change Nest CLI config:\n\n```json\n// nest-cli.json\n\n{\n  ...,\n  \"compilerOptions\": {\n    ...,\n    \"builder\": \"tsc\" // type \"swc\" to return back to SWC\n  }\n}\n```\n\nAnd change Jest config for tests:\n```json\n// jest-e2e.json\n\n{\n  ...,\n  \"transform\": {\n    \"^.+\\\\.(t|j)s?$\": [\"ts-jest\"] // replace with \"@swc/jest\" to return back to SWC\n  },\n}\n```\n\n## Pagination\nPagination is available for all endpoints that return an array of objects. The default page size is 10. You can change the default page size by setting the `DEFAULT_PAGE_SIZE` environment variable.\nWe are using the [nestjs-prisma-pagination](https://www.npmjs.com/package/@nodeteam/nestjs-prisma-pagination) library for pagination.\n\nExample of a paginated response:\n\n```typescript\n{\n    data: T[],\n    meta: {\n        total: number,\n        lastPage: number,\n        currentPage: number,\n        perPage: number,\n        prev: number | null,\n        next: number | null,\n  },\n}\n```\n\n## Query Builder\nThe query builder is available for all endpoints that return an array of objects. You can use the query builder to filter, sort, and paginate the results.\nWe are using the [nestjs-pipes](https://www.npmjs.com/package/@nodeteam/nestjs-pipes) library for the query builder.\n\nExample of a query builder request:\n\n```\nGET /user/?where=firstName:John\n```\n\n```typescript\n    @Get()\n    @ApiQuery({ name: 'where', required: false, type: 'string' })\n    @ApiQuery({ name: 'orderBy', required: false, type: 'string' })\n    @UseGuards(AccessGuard)\n    @Serialize(UserBaseEntity)\n    @UseAbility(Actions.read, TokensEntity)\n    findAll(\n        @Query('where', WherePipe) where?: Prisma.UserWhereInput,\n        @Query('orderBy', OrderByPipe) orderBy?: Prisma.UserOrderByWithRelationInput,\n    ): Promise\u003cPaginatorTypes.PaginatedResult\u003cUser\u003e\u003e {\n        return this.userService.findAll(where, orderBy);\n    }\n```\n\n## Swagger\nSwagger documentation is available at http://localhost:3000/docs\n\n## JWT\n\n### AuthGuard\nBy default, `AuthGuard` will look for a JWT in the `Authorization` header with the scheme `Bearer`. You can customize this behavior by passing an options object to the `AuthGuard` decorator.\nAll routes that are protected by the `AuthGuard` decorator will require a valid JWT token in the `Authorization` header of the incoming request.\n    \n```typescript\n// app.module.ts\n\nproviders: [\n    {\n        provide: APP_GUARD,\n        useClass: AuthGuard,\n    },\n]\n```\n\n### SkipAuth\nYou can skip authentication for a route by using the `SkipAuth` decorator.\n\n```typescript\n// app.controller.ts\n\n@SkipAuth()\n@Get()\nasync findAll() {\n    return await this.appService.findAll();\n}\n```\n\n## CASL\n\n### Roles configuration\n\nDefine roles for app:\n\n```typescript\n// app.roles.ts\n\nexport enum Roles {\n  admin = 'admin',\n  customer = 'customer',\n}\n```\n\n## Permissions definition\n\n`nest-casl` comes with a set of default actions, aligned with [Nestjs Query](https://doug-martin.github.io/nestjs-query/docs/graphql/authorization).\n`manage` has a special meaning of any action.\nDefaultActions aliased to `Actions` for convenicence.\n\n```typescript\nexport enum DefaultActions {\n  read = 'read',\n  aggregate = 'aggregate',\n  create = 'create',\n  update = 'update',\n  delete = 'delete',\n  manage = 'manage',\n}\n```\n\nIn case you need custom actions either [extend DefaultActions](#custom-actions) or just copy and update, if extending typescript enum looks too tricky.\n\nPermissions defined per module. `everyone` permissions applied to every user, it has `every` alias for `every({ user, can })` be more readable. Roles can be extended with previously defined roles.\n\n```typescript\n// post.permissions.ts\n\nimport { Permissions, Actions } from 'nest-casl';\nimport { InferSubjects } from '@casl/ability';\n\nimport { Roles } from '../app.roles';\nimport { Post } from './dtos/post.dto';\nimport { Comment } from './dtos/comment.dto';\n\nexport type Subjects = InferSubjects\u003ctypeof Post, typeof Comment\u003e;\n\nexport const permissions: Permissions\u003cRoles, Subjects, Actions\u003e = {\n  everyone({ can }) {\n    can(Actions.read, Post);\n    can(Actions.create, Post);\n  },\n\n  customer({ user, can }) {\n    can(Actions.update, Post, { userId: user.id });\n  },\n\n  operator({ can, cannot, extend }) {\n    extend(Roles.customer);\n\n    can(Actions.manage, PostCategory);\n    can(Actions.manage, Post);\n    cannot(Actions.delete, Post);\n  },\n};\n```\n\n```typescript\n// post.module.ts\n\nimport { Module } from '@nestjs/common';\nimport { CaslModule } from 'nest-cast';\n\nimport { permissions } from './post.permissions';\n\n@Module({\n  imports: [CaslModule.forFeature({ permissions })],\n})\nexport class PostModule {}\n```\n\n## CaslUser decorator\nCaslUser decorator provides access to lazy loaded user, obtained from request or user hook and cached on request object.\n\n```typescript\n    @UseGuards(AuthGuard, AccessGuard)\n    @UseAbility(Actions.update, Post)\n    async updatePostConditionParamNoHook(\n      @Args('input') input: UpdatePostInput,\n      @CaslUser() userProxy: UserProxy\u003cUser\u003e\n    ) {\n    const user = await userProxy.get();\n    }\n```\n\n## User Hook\n\nSometimes permission conditions require more info on user than exists on `request.user` User hook called after `getUserFromRequest` only for abilities with conditions. Similar to subject hook, it can be class or tuple.\nDespite UserHook is configured on application level, it is executed in context of modules under authorization. To avoid importing user service to each module, consider making user module global.\n\n```typescript\n// user.hook.ts\n\nimport { Injectable } from '@nestjs/common';\n\nimport { UserBeforeFilterHook } from 'nest-casl';\nimport { UserService } from './user.service';\nimport { User } from './dtos/user.dto';\n\n@Injectable()\nexport class UserHook implements UserBeforeFilterHook\u003cUser\u003e {\n  constructor(readonly userService: UserService) {}\n\n  async run(user: User) {\n    return {\n      ...user,\n      ...(await this.userService.findById(user.id)),\n    };\n  }\n}\n```\n\n```typescript\n//app.module.ts\n\nimport { Module } from '@nestjs/common';\nimport { CaslModule } from 'nest-casl';\n\n@Module({\n  imports: [\n    CaslModule.forRoot({\n      getUserFromRequest: (request) =\u003e request.user,\n      getUserHook: UserHook,\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nor with dynamic module initialization\n\n```typescript\n//app.module.ts\n\nimport { Module } from '@nestjs/common';\nimport { CaslModule } from 'nest-casl';\n\n@Module({\n  imports: [\n    CaslModule.forRootAsync({\n      useFactory: async (service: SomeCoolService) =\u003e {\n        const isOk = await service.doSomething();\n\n        return {\n          getUserFromRequest: () =\u003e {\n            if (isOk) {\n              return request.user;\n            }\n          },\n        };\n      },\n      inject: [SomeCoolService],\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nor with tuple hook\n\n```typescript\n//app.module.ts\n\nimport { Module } from '@nestjs/common';\nimport { CaslModule } from 'nest-casl';\n\n@Module({\n  imports: [\n    CaslModule.forRoot({\n      getUserFromRequest: (request) =\u003e request.user,\n      getUserHook: [\n        UserService,\n        async (service: UserService, user) =\u003e {\n          return service.findById(user.id);\n        },\n      ],\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### Custom actions\n\nExtending enums is a bit tricky in TypeScript\nThere are multiple solutions described in [this issue](https://github.com/microsoft/TypeScript/issues/17592) but this one is the simplest:\n\n```typescript\nenum CustomActions {\n  feature = 'feature',\n}\n\nexport type Actions = DefaultActions | CustomActions;\nexport const Actions = { ...DefaultActions, ...CustomActions };\n```\n\n### Custom User and Request types\n\nFor example, if you have User with numeric id and current user assigned to `request.loggedInUser`\n\n```typescript\nclass User implements AuthorizableUser\u003cRoles, number\u003e {\n  id: number;\n  roles: Array\u003cRoles\u003e;\n}\n\ninterface CustomAuthorizableRequest {\n  loggedInUser: User;\n}\n\n@Module({\n  imports: [\n    CaslModule.forRoot\u003cRoles, User, CustomAuthorizableRequest\u003e({\n      superuserRole: Roles.admin,\n      getUserFromRequest(request) {\n        return request.loggedInUser;\n      },\n      getUserHook: [\n        UserService,\n        async (service: UserService, user) =\u003e {\n          return service.findById(user.id);\n        },\n      ],\n    }),\n    //  ...\n  ],\n})\nexport class AppModule {}\n```\n\n## Prisma \n\n### Configuration\n\n---\ntitle: Configuration\n---\n\n`PrismaModule` provides a `forRoot(...)` and `forRootAsync(..)` method. They accept an option object of `PrismaModuleOptions` for the [PrismaService](#prismaservice-options) and [PrismaClient](#prismaclient-options).\n\n## PrismaService options\n\n### isGlobal\n\nIf `true`, registers `PrismaModule` as a [global](https://docs.nestjs.com/modules#global-modules) module. `PrismaService`will be available everywhere.\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PrismaModule } from 'nestjs-prisma';\n\n@Module({\n  imports: [\n    PrismaModule.forRoot({\n      isGlobal: true,\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### prismaServiceOptions.explicitConnect\n\nIf `true`, `PrismaClient` explicitly creates a connection pool and your first query will respond instantly.\n\nFor most use cases the [lazy connect](https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management) behavior of `PrismaClient` will do. The first query of `PrismaClient` creates the connection pool.\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PrismaModule } from 'nestjs-prisma';\n\n@Module({\n  imports: [\n    PrismaModule.forRoot({\n      prismaServiceOptions: {\n        explicitConnect: true,\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n## PrismaClient options\n\n### prismaServiceOptions.prismaOptions\n\nPass `PrismaClientOptions` [options](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference/#prismaclient) directly to the `PrismaClient`.\n\n### prismaServiceOptions.middlewares\n\nApply Prisma [middlewares](/docs/prisma-middlewares) to perform actions before or after db queries.\n\n## Async configuration\n\nAdditionally, `PrismaModule` provides a `forRootAsync` to pass options asynchronously.\n\n### useFactory\n\nOne option is to use a factory function:\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PrismaModule } from 'nestjs-prisma';\n\n@Module({\n  imports: [\n    PrismaModule.forRootAsync({\n      isGlobal: true,\n      useFactory: () =\u003e ({\n        prismaOptions: {\n          log: ['info', 'query'],\n        },\n        explicitConnect: false,\n      }),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nYou can inject dependencies such as `ConfigModule` to load options from .env files.\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { PrismaModule } from 'nestjs-prisma';\n\n@Module({\n  imports: [\n    ConfigModule.forRoot({\n      isGlobal: true,\n    }),\n    PrismaModule.forRootAsync({\n      isGlobal: true,\n      useFactory: async (configService: ConfigService) =\u003e {\n        return {\n          prismaOptions: {\n            log: [configService.get('log')],\n            datasources: {\n              db: {\n                url: configService.get('DATABASE_URL'),\n              },\n            },\n          },\n          explicitConnect: configService.get('explicit'),\n        };\n      },\n      inject: [ConfigService],\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### useClass\n\nAlternatively, you can use a class instead of a factory:\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PrismaModule } from 'nestjs-prisma';\n\n@Module({\n  imports: [\n    ConfigModule.forRoot({\n      isGlobal: true,\n    }),\n    PrismaModule.forRootAsync({\n      isGlobal: true,\n      useClass: PrismaConfigService,\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nCreate the `PrismaConfigService` and extend it with the `PrismaOptionsFactory`\n\n```ts\nimport { Injectable } from '@nestjs/common';\nimport { PrismaOptionsFactory, PrismaServiceOptions } from 'nestjs-prisma';\n\n@Injectable()\nexport class PrismaConfigService implements PrismaOptionsFactory {\n  constructor() {\n    // TODO inject any other service here like the `ConfigService`\n  }\n\n  createPrismaOptions(): PrismaServiceOptions | Promise\u003cPrismaServiceOptions\u003e {\n    return {\n      prismaOptions: {\n        log: ['info', 'query'],\n      },\n      explicitConnect: true,\n    };\n  }\n}\n```\n\n### Prisma Middleware\n\nApply [Prisma Middlewares](https://www.prisma.io/docs/concepts/components/prisma-client/middleware) with `PrismaModule`\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PrismaModule } from 'nestjs-prisma';\n\n@Module({\n  imports: [\n    PrismaModule.forRoot({\n      prismaServiceOptions: {\n        middlewares: [\n          async (params, next) =\u003e {\n            // Before query: change params\n            const result = await next(params);\n            // After query: result\n            return result;\n          },\n        ], // see example loggingMiddleware below\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nHere is an example for using a [Logging middleware](https://www.prisma.io/docs/concepts/components/prisma-client/middleware/logging-middleware).\n\nCreate your Prisma Middleware and export it as a `function`\n\n```ts\n// src/logging-middleware.ts\nimport { Prisma } from '@prisma/client';\n\nexport function loggingMiddleware(): Prisma.Middleware {\n  return async (params, next) =\u003e {\n    const before = Date.now();\n\n    const result = await next(params);\n\n    const after = Date.now();\n\n    console.log(\n      `Query ${params.model}.${params.action} took ${after - before}ms`\n    );\n\n    return result;\n  };\n}\n```\n\nNow import your middleware and add the function into the `middlewares` array.\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PrismaModule } from 'nestjs-prisma';\nimport { loggingMiddleware } from './logging-middleware';\n\n@Module({\n  imports: [\n    PrismaModule.forRoot({\n      prismaServiceOptions: {\n        middlewares: [loggingMiddleware()],\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fv-checha%2Fnestjs-prisma-mongodb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fv-checha%2Fnestjs-prisma-mongodb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fv-checha%2Fnestjs-prisma-mongodb/lists"}