feat: added graphql examples

This commit is contained in:
user
2021-09-21 19:13:00 +02:00
parent 6006f76feb
commit f8adab3bbe
13 changed files with 2168 additions and 104 deletions

View File

@@ -8,6 +8,7 @@ Added more code examples:
- All Domain Events can now be executed in a single database transaction using a UnitOfWork
- Added Wallet module to show an example of using a UnitOfWork together with Domain Events
- Added BDD tests example
- Added GraphQL examples
Refactoring:
@@ -698,7 +699,7 @@ Contains `Controllers` and `Request`/`Response` DTOs (can also contain `Views`,
## Controllers
- Controllers are used for parsing requests, triggering use cases and presenting the result back to the client.
- Controller is a user-facing API that is used for parsing requests, triggering business logic and presenting the result back to the client.
- One controller per use case is considered a good practice.
- In [NestJS](https://docs.nestjs.com/) world controllers may be a good place to use [OpenAPI/Swagger decorators](https://docs.nestjs.com/openapi/operations) for documentation.
@@ -709,6 +710,14 @@ One controller per trigger type can be used to have a more clear separation. For
- [create-user.message.controller.ts](src/modules/user/commands/create-user/create-user.message.controller.ts) for external messages ([NetJS Microservices](https://docs.nestjs.com/microservices/basics)).
- etc.
### Resolvers
If you are using [GraphQL](https://graphql.org/) instead of controllers you will use [Resolvers](https://docs.nestjs.com/graphql/resolvers).
Example files:
- [create-user.graphql-resolver.ts](src/modules/user/commands/create-user/create-user.graphql-resolver.ts)
---
## DTOs

2112
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,15 +28,19 @@
"seed:run": "ts-node -r tsconfig-paths/register --project ./tsconfig.json ./node_modules/typeorm-seeding/dist/cli.js seed --root src/infrastructure/configs"
},
"dependencies": {
"@apollo/gateway": "^0.41.0",
"@nestjs/common": "^7.0.0",
"@nestjs/core": "^7.0.0",
"@nestjs/graphql": "^9.0.4",
"@nestjs/microservices": "^7.6.12",
"@nestjs/platform-express": "^7.0.0",
"@nestjs/swagger": "^4.7.5",
"@nestjs/typeorm": "^7.1.5",
"apollo-server-express": "^3.3.0",
"class-transformer": "^0.3.1",
"class-validator": "^0.12.2",
"dotenv": "^8.2.0",
"graphql": "^15.6.0",
"nanoid": "^3.1.25",
"nest-event": "^1.0.8",
"nestjs-console": "^7.0.0",
@@ -45,10 +49,12 @@
"rimraf": "^3.0.2",
"rxjs": "^6.5.4",
"swagger-ui-express": "^4.1.5",
"ts-morph": "^12.0.0",
"typeorm": "^0.2.29",
"typeorm-seeding": "^1.6.1",
"uuid": "^8.3.1",
"validator": "^13.1.17"
"validator": "^13.1.17",
"ws": "^8.2.2"
},
"devDependencies": {
"@nestjs/cli": "^7.0.0",
@@ -61,6 +67,7 @@
"@types/node": "^13.9.1",
"@types/supertest": "^2.0.8",
"@types/uuid": "^8.3.0",
"@types/ws": "^7.4.7",
"@typescript-eslint/eslint-plugin": "3.9.1",
"@typescript-eslint/parser": "3.9.1",
"eslint": "7.7.0",

View File

@@ -4,11 +4,17 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { NestEventModule } from 'nest-event';
import { ConsoleModule } from 'nestjs-console';
import { WalletModule } from '@modules/wallet/wallet.module';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { typeormConfig } from './infrastructure/configs/ormconfig';
@Module({
imports: [
TypeOrmModule.forRoot(typeormConfig),
// only if you are using GraphQL
GraphQLModule.forRoot({
autoSchemaFile: join(process.cwd(), 'src/infrastructure/schema.gql'),
}),
NestEventModule,
ConsoleModule,
UserModule,

View File

@@ -0,0 +1,35 @@
# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------
type IdResponse {
id: String!
}
type UserResponse {
email: String!
country: String!
postalCode: String!
street: String!
}
type Query {
findUsers(input: FindUsersRequest!): UserResponse!
}
input FindUsersRequest {
country: String!
postalCode: String!
street: String!
}
type Mutation {
create(input: CreateUserRequest!): IdResponse!
}
input CreateUserRequest {
email: String!
country: String!
postalCode: String!
street: String!
}

View File

@@ -1,11 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';
import { Field, ObjectType } from '@nestjs/graphql';
import { Id } from '../interfaces/id.interface';
@ObjectType() // <- only if you are using GraphQL
export class IdResponse implements Id {
constructor(id: string) {
this.id = id;
}
@ApiProperty({ example: '2cdc8ab1-6d50-49cc-ba14-54e4ac7ec231' })
@Field() // <- only if you are using GraphQL
id: string;
}

View File

@@ -0,0 +1,26 @@
import { createUserSymbol } from '@modules/user/user.providers';
import { Inject } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { IdResponse } from '@libs/ddd/interface-adapters/dtos/id.response.dto';
import { CreateUserCommand } from './create-user.command';
import { CreateUserRequest } from './create-user.request.dto';
import { CreateUserService } from './create-user.service';
// If you are Using GraphQL you'll need a Resolver instead of a Controller
@Resolver()
export class CreateUserGraphqlResolver {
constructor(
@Inject(createUserSymbol)
private readonly service: CreateUserService,
) {}
@Mutation(() => IdResponse)
async create(@Args('input') input: CreateUserRequest): Promise<IdResponse> {
const command = new CreateUserCommand(input);
const id = await this.service.executeUnitOfWork(command);
return new IdResponse(id.value);
}
}

View File

@@ -1,11 +1,12 @@
import { createUserSymbol } from '@modules/user/user.providers';
import { Inject } from '@nestjs/common';
import { Controller, Inject } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { IdResponse } from '@libs/ddd/interface-adapters/dtos/id.response.dto';
import { CreateUserCommand } from './create-user.command';
import { CreateUserMessageRequest } from './create-user.request.dto';
import { CreateUserService } from './create-user.service';
@Controller()
export class CreateUserMessageController {
constructor(
@Inject(createUserSymbol)

View File

@@ -1,5 +1,6 @@
import { CreateUser } from '@src/interface-adapters/interfaces/user/create.user.interface';
import { ApiProperty } from '@nestjs/swagger';
import { ArgsType, Field, InputType } from '@nestjs/graphql';
import {
IsAlphanumeric,
IsEmail,
@@ -8,6 +9,8 @@ import {
MaxLength,
} from 'class-validator';
@ArgsType() // <- only if you are using GraphQL
@InputType() // <- only if you are using GraphQL
export class CreateUserRequest implements CreateUser {
@ApiProperty({
example: 'john@gmail.com',
@@ -15,22 +18,26 @@ export class CreateUserRequest implements CreateUser {
})
@MaxLength(320)
@IsEmail()
@Field() // <- only if you are using graphql
readonly email: string;
@ApiProperty({ example: 'France', description: 'Country of residence' })
@MaxLength(50)
@IsString()
@Matches(/^[a-zA-Z ]*$/)
@Field() // <- only if you are using graphql
readonly country: string;
@ApiProperty({ example: '28566', description: 'Postal code' })
@MaxLength(10)
@IsAlphanumeric()
@Field() // <- only if you are using graphql
readonly postalCode: string;
@ApiProperty({ example: 'Grande Rue', description: 'Street' })
@MaxLength(50)
@Matches(/^[a-zA-Z ]*$/)
@Field() // <- only if you are using graphql
readonly street: string;
}

View File

@@ -2,7 +2,9 @@ import { UserEntity } from '@modules/user/domain/entities/user.entity';
import { ResponseBase } from '@libs/ddd/interface-adapters/base-classes/response.base';
import { User } from '@src/interface-adapters/interfaces/user/user.interface';
import { ApiProperty } from '@nestjs/swagger';
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType() // only if you are using graphql
export class UserResponse extends ResponseBase implements User {
constructor(user: UserEntity) {
super(user);
@@ -24,24 +26,28 @@ export class UserResponse extends ResponseBase implements User {
example: 'joh-doe@gmail.com',
description: "User's email address",
})
@Field() // <- only if you are using GraphQL
email: string;
@ApiProperty({
example: 'France',
description: "User's country of residence",
})
@Field() // <- only if you are using GraphQL
country: string;
@ApiProperty({
example: '123456',
description: 'Postal code',
})
@Field() // <- only if you are using GraphQL
postalCode: string;
@ApiProperty({
example: 'Park Avenue',
description: 'Street where the user is registered',
})
@Field() // <- only if you are using GraphQL
street: string;
}

View File

@@ -0,0 +1,20 @@
import { UserResponse } from '@modules/user/dtos/user.response.dto';
import { Args, Query, Resolver } from '@nestjs/graphql';
import { UserRepository } from '@modules/user/database/user.repository';
import { FindUsersQuery } from './find-users.query';
import { FindUsersRequest } from './find-users.request.dto';
@Resolver()
export class FindUsersGraphqlResolver {
constructor(private readonly userRepo: UserRepository) {}
@Query(() => UserResponse)
async findUsers(
@Args('input') input: FindUsersRequest,
): Promise<UserResponse[]> {
const query = new FindUsersQuery(input);
const users = await this.userRepo.findUsers(query);
return users.map(user => new UserResponse(user));
}
}

View File

@@ -7,25 +7,31 @@ import {
IsOptional,
} from 'class-validator';
import { FindUsers } from '@src/interface-adapters/interfaces/user/find-users.interface';
import { ArgsType, Field, InputType } from '@nestjs/graphql';
@ArgsType() // <- only if you are using GraphQL
@InputType()
export class FindUsersRequest implements FindUsers {
@ApiProperty({ example: 'France', description: 'Country of residence' })
@MaxLength(50)
@IsOptional()
@IsString()
@Matches(/^[a-zA-Z ]*$/)
@Field() // <- only if you are using GraphQL
readonly country: string;
@ApiProperty({ example: '28566', description: 'Postal code' })
@IsOptional()
@MaxLength(10)
@IsAlphanumeric()
@Field() // <- only if you are using GraphQL
readonly postalCode: string;
@ApiProperty({ example: 'Grande Rue', description: 'Street' })
@IsOptional()
@MaxLength(50)
@Matches(/^[a-zA-Z ]*$/)
@Field() // <- only if you are using GraphQL
readonly street: string;
}

View File

@@ -11,19 +11,33 @@ import {
} from './user.providers';
import { CreateUserCliController } from './commands/create-user/create-user.cli.controller';
import { FindUsersHttpController } from './queries/find-users/find-users.http.controller';
import { CreateUserMessageController } from './commands/create-user/create-user.message.controller';
import { CreateUserGraphqlResolver } from './commands/create-user/create-user.graphql-resolver';
import { FindUsersGraphqlResolver } from './queries/find-users/find-users.gralhql-resolver';
const httpControllers = [
CreateUserHttpController,
DeleteUserHttpController,
FindUsersHttpController,
];
const messageControllers = [CreateUserMessageController];
const cliControllers = [CreateUserCliController];
const graphqlResolvers = [CreateUserGraphqlResolver, FindUsersGraphqlResolver];
const repositories = [UserRepository];
@Module({
imports: [TypeOrmModule.forFeature([UserOrmEntity])],
controllers: [
CreateUserHttpController,
DeleteUserHttpController,
FindUsersHttpController,
],
controllers: [...httpControllers, ...messageControllers],
providers: [
UserRepository,
...cliControllers,
...repositories,
...graphqlResolvers,
createUserProvider,
removeUserProvider,
CreateUserCliController,
createUserCliLoggerProvider,
],
})