|
| 1 | +### CRUD (resource) generator |
| 2 | + |
| 3 | +Throughout the life span of a project, when we build new features, we often need to add new resources to our application. These resources typically require multiple, repetitive operations that we have to repeat each time we define a new resource. |
| 4 | + |
| 5 | +#### Introduction |
| 6 | + |
| 7 | +Let's imagine a real-world scenario, where we need to expose CRUD endpoints for 2 entities, let's say **User** and **Product** entities. |
| 8 | +Following the best practices, for each entity we would have to perform several operations, as follows: |
| 9 | + |
| 10 | +- Generate a module (`nest g mo`) to keep code organized and establish clear boundaries (grouping related components) |
| 11 | +- Generate a controller (`nest g co`) to define CRUD routes (or queries/mutations for GraphQL applications) |
| 12 | +- Generate a service (`nest g s`) to implement & isolate business logic |
| 13 | +- Generate an entity class/interface to represent the resource data shape |
| 14 | +- Generate Data Transfer Objects (or inputs for GraphQL applications) to define how the data will be sent over the network |
| 15 | + |
| 16 | +That's a lot of steps! |
| 17 | + |
| 18 | +To help speed up this repetitive process, [NestJS CLI](https://docs.nestjs.com/cli/overview) provides a new generator (schematic) that automatically generates all the boilerplate code to help us avoid doing all of this, and make the developer experience much simpler. |
| 19 | + |
| 20 | +> info **NOTE** The schematic supports generating **HTTP** controllers, **Microservice** controllers, **GraphQL** resolvers (both code first and schema first), and **WebSocket** Gateways. |
| 21 | +
|
| 22 | +#### Generating a new resource |
| 23 | + |
| 24 | +To create a new resource, simply run the following command in the root directory of your project: |
| 25 | + |
| 26 | +```shell |
| 27 | +$ nest g resource |
| 28 | +``` |
| 29 | + |
| 30 | +`nest g resource` command not only generates all the NestJS building blocks (module, service, controller classes) but also an entity class, DTO classes as well as the testing (`.spec`) files. |
| 31 | + |
| 32 | +Below you can see the generated controller file (for REST API): |
| 33 | + |
| 34 | +```typescript |
| 35 | +@Controller('users') |
| 36 | +export class UsersController { |
| 37 | + constructor(private readonly usersService: UsersService) {} |
| 38 | + |
| 39 | + @Post() |
| 40 | + create(@Body() createUserDto: CreateUserDto) { |
| 41 | + return this.usersService.create(createUserDto); |
| 42 | + } |
| 43 | + |
| 44 | + @Get() |
| 45 | + findAll() { |
| 46 | + return this.usersService.findAll(); |
| 47 | + } |
| 48 | + |
| 49 | + @Get(':id') |
| 50 | + findOne(@Param('id') id: string) { |
| 51 | + return this.usersService.findOne(+id); |
| 52 | + } |
| 53 | + |
| 54 | + @Put(':id') |
| 55 | + update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { |
| 56 | + return this.usersService.update(+id, updateUserDto); |
| 57 | + } |
| 58 | + |
| 59 | + @Delete(':id') |
| 60 | + remove(@Param('id') id: string) { |
| 61 | + return this.usersService.remove(+id); |
| 62 | + } |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +Also, it automatically creates placeholders for all the CRUD endpoints (routes for REST APIs, queries and mutations for GraphQL, message subscribes for both Microservices and WebSocket Gateways) - all without having to lift a finger. |
| 67 | + |
| 68 | +> warning **NOTE** Generated service classes are **not** tied to any specific **ORM (or data source)**. This makes the generator generic enough to meet the needs of any project. By default, all methods will contain placeholders, allowing you to populate it with the data sources specific to your project. |
| 69 | +
|
| 70 | +Likewise, if you want to generate resolvers for a GraphQL application, simply select the `GraphQL (code first)` (or `GraphQL (schema first)`) as your transport layer. |
| 71 | + |
| 72 | +In this case, NestJS will generate a resolver class instead of a REST API controller: |
| 73 | + |
| 74 | +```shell |
| 75 | +$ nest g resource users |
| 76 | + |
| 77 | +> ? What transport layer do you use? GraphQL (code first) |
| 78 | +> ? Would you like to generate CRUD entry points? Yes |
| 79 | +> CREATE src/users/users.module.ts (224 bytes) |
| 80 | +> CREATE src/users/users.resolver.spec.ts (525 bytes) |
| 81 | +> CREATE src/users/users.resolver.ts (1109 bytes) |
| 82 | +> CREATE src/users/users.service.spec.ts (453 bytes) |
| 83 | +> CREATE src/users/users.service.ts (625 bytes) |
| 84 | +> CREATE src/users/dto/create-user.input.ts (195 bytes) |
| 85 | +> CREATE src/users/dto/update-user.input.ts (281 bytes) |
| 86 | +> CREATE src/users/entities/user.entity.ts (187 bytes) |
| 87 | +> UPDATE src/app.module.ts (312 bytes) |
| 88 | +``` |
| 89 | + |
| 90 | +> info **Hint** To avoid generating test files, you can pass the `--no-spec` flag, as follows: `nest g resource users --no-spec` |
| 91 | +
|
| 92 | +We can see below, that not only were all boilerplate mutations and queries created, but everything is all tied together. We're utilizing the `UsersService`, `User` Entity, and our DTO's. |
| 93 | + |
| 94 | +```typescript |
| 95 | +import { Resolver, Query, Mutation, Args, Int } from '@nestjs/graphql'; |
| 96 | +import { UsersService } from './users.service'; |
| 97 | +import { User } from './entities/user.entity'; |
| 98 | +import { CreateUserInput } from './dto/create-user.input'; |
| 99 | +import { UpdateUserInput } from './dto/update-user.input'; |
| 100 | + |
| 101 | +@Resolver(() => User) |
| 102 | +export class UsersResolver { |
| 103 | + constructor(private readonly usersService: UsersService) {} |
| 104 | + |
| 105 | + @Mutation(() => User) |
| 106 | + createUser(@Args('createUserInput') createUserInput: CreateUserInput) { |
| 107 | + return this.usersService.create(createUserInput); |
| 108 | + } |
| 109 | + |
| 110 | + @Query(() => [User], { name: 'users' }) |
| 111 | + findAll() { |
| 112 | + return this.usersService.findAll(); |
| 113 | + } |
| 114 | + |
| 115 | + @Query(() => User, { name: 'user' }) |
| 116 | + findOne(@Args('id', { type: () => Int }) id: number) { |
| 117 | + return this.usersService.findOne(id); |
| 118 | + } |
| 119 | + |
| 120 | + @Mutation(() => User) |
| 121 | + updateUser(@Args('updateUserInput') updateUserInput: UpdateUserInput) { |
| 122 | + return this.usersService.update(updateUserInput.id, updateUserInput); |
| 123 | + } |
| 124 | + |
| 125 | + @Mutation(() => User) |
| 126 | + removeUser(@Args('id', { type: () => Int }) id: number) { |
| 127 | + return this.usersService.remove(id); |
| 128 | + } |
| 129 | +} |
| 130 | +``` |
0 commit comments