feat: added graphql examples
This commit is contained in:
11
README.md
11
README.md
@@ -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
2112
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
35
src/infrastructure/schema.gql
Normal file
35
src/infrastructure/schema.gql
Normal 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!
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user