Skip to content

Commit 62c1799

Browse files
author
Gery Hirschfeld
authored
Merge pull request #175 from epiphone/generate-openapi
Generate OpenAPI spec automatically
2 parents 954e9fa + 7ac5cb8 commit 62c1799

12 files changed

+242
-683
lines changed

.env.example

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ GRAPHQL_EDITOR=true
6565
#
6666
SWAGGER_ENABLED=true
6767
SWAGGER_ROUTE=/swagger
68-
SWAGGER_FILE=api/swagger.json
6968
SWAGGER_USERNAME=admin
7069
SWAGGER_PASSWORD=1234
7170

.env.test

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ GRAPHQL_EDITOR=false
4747
#
4848
SWAGGER_ENABLED=true
4949
SWAGGER_ROUTE=/swagger
50-
SWAGGER_FILE=api/swagger.json
5150
SWAGGER_USERNAME=admin
5251
SWAGGER_PASSWORD=1234
5352

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Try it!! We are happy to hear your feedback or any kind of new features.
4646
- **Easy Exception Handling** thanks to [routing-controllers](https://github.com/pleerock/routing-controllers).
4747
- **Smart Validation** thanks to [class-validator](https://github.com/pleerock/class-validator) with some nice annotations.
4848
- **Custom Validators** to validate your request even better and stricter. [custom-validation-classes](https://github.com/pleerock/class-validator#custom-validation-classes).
49-
- **API Documentation** thanks to [swagger](http://swagger.io/).
49+
- **API Documentation** thanks to [swagger](http://swagger.io/) and [routing-controllers-openapi](https://github.com/epiphone/routing-controllers-openapi).
5050
- **API Monitoring** thanks to [express-status-monitor](https://github.com/RafalWilinski/express-status-monitor).
5151
- **Integrated Testing Tool** thanks to [Jest](https://facebook.github.io/jest).
5252
- **E2E API Testing** thanks to [supertest](https://github.com/visionmedia/supertest).
@@ -215,7 +215,6 @@ The swagger and the monitor route can be altered in the `.env` file.
215215
| **src/api/resolvers/** | GraphQL resolvers (query, mutation & field-resolver) |
216216
| **src/api/types/** | GraphQL types ,input-types and scalar types |
217217
| **src/api/** schema.gql | Generated GraphQL schema |
218-
| **src/api/** swagger.json | Swagger documentation |
219218
| **src/auth/** | Authentication checkers and services |
220219
| **src/core/** | The core features like logger and env variables |
221220
| **src/database/factories** | Factory the generate fake entities |

package-scripts.js

-8
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,10 @@ module.exports = {
106106
copy: {
107107
default: {
108108
script: series(
109-
`nps copy.swagger`,
110109
`nps copy.public`
111110
),
112111
hiddenFromHelp: true
113112
},
114-
swagger: {
115-
script: copy(
116-
'./src/api/swagger.json',
117-
'./dist'
118-
),
119-
hiddenFromHelp: true
120-
},
121113
public: {
122114
script: copy(
123115
'./src/public/*',

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"bcrypt": "3.0.1",
4242
"chalk": "^2.4.1",
4343
"class-validator": "^0.9.1",
44+
"class-validator-jsonschema": "^1.3.0",
4445
"commander": "^2.19.0",
4546
"compression": "^1.7.1",
4647
"copyfiles": "^2.1.0",
@@ -67,6 +68,7 @@
6768
"pg": "^7.4.3",
6869
"reflect-metadata": "^0.1.10",
6970
"routing-controllers": "^0.7.6",
71+
"routing-controllers-openapi": "^1.7.0",
7072
"serve-favicon": "^2.4.5",
7173
"supertest": "^3.0.0",
7274
"swagger-ui-express": "4.0.1",
@@ -77,7 +79,7 @@
7779
"typeorm": "^0.2.5",
7880
"typeorm-seeding": "^1.0.0-beta.6",
7981
"typeorm-typedi-extensions": "^0.2.1",
80-
"typescript": "3.0.3",
82+
"typescript": "^3.6.3",
8183
"uuid": "^3.3.2",
8284
"winston": "3.1.0"
8385
},
@@ -113,6 +115,7 @@
113115
"@types/jest": "23.3.2",
114116
"@types/morgan": "^1.7.35",
115117
"@types/nock": "^9.1.3",
118+
"@types/node": "^12.7.5",
116119
"@types/reflect-metadata": "0.1.0",
117120
"@types/serve-favicon": "^2.2.29",
118121
"@types/supertest": "^2.0.4",

src/api/controllers/PetController.ts

+40-2
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,75 @@
1+
import { IsNotEmpty, IsNumber, IsUUID, ValidateNested } from 'class-validator';
12
import {
23
Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put
34
} from 'routing-controllers';
5+
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
46

57
import { PetNotFoundError } from '../errors/PetNotFoundError';
68
import { Pet } from '../models/Pet';
79
import { PetService } from '../services/PetService';
10+
import { UserResponse } from './UserController';
11+
12+
class BasePet {
13+
@IsNotEmpty()
14+
public name: string;
15+
16+
@IsNumber()
17+
public age: number;
18+
}
19+
20+
export class PetResponse extends BasePet {
21+
@IsUUID()
22+
public id: string;
23+
24+
@ValidateNested()
25+
public user: UserResponse;
26+
}
27+
28+
class CreatePetBody extends BasePet {
29+
@IsUUID()
30+
public userId: string;
31+
}
832

933
@Authorized()
1034
@JsonController('/pets')
35+
@OpenAPI({ security: [{ basicAuth: [] }] })
1136
export class PetController {
1237

1338
constructor(
1439
private petService: PetService
1540
) { }
1641

1742
@Get()
43+
@ResponseSchema(PetResponse, { isArray: true })
1844
public find(): Promise<Pet[]> {
1945
return this.petService.find();
2046
}
2147

2248
@Get('/:id')
2349
@OnUndefined(PetNotFoundError)
50+
@ResponseSchema(PetResponse)
2451
public one(@Param('id') id: string): Promise<Pet | undefined> {
2552
return this.petService.findOne(id);
2653
}
2754

2855
@Post()
29-
public create(@Body() pet: Pet): Promise<Pet> {
56+
@ResponseSchema(PetResponse)
57+
public create(@Body({ required: true }) body: CreatePetBody): Promise<Pet> {
58+
const pet = new Pet();
59+
pet.age = body.age;
60+
pet.name = body.name;
61+
pet.userId = body.userId;
62+
3063
return this.petService.create(pet);
3164
}
3265

3366
@Put('/:id')
34-
public update(@Param('id') id: string, @Body() pet: Pet): Promise<Pet> {
67+
@ResponseSchema(PetResponse)
68+
public update(@Param('id') id: string, @Body() body: BasePet): Promise<Pet> {
69+
const pet = new Pet();
70+
pet.age = body.age;
71+
pet.name = body.name;
72+
3573
return this.petService.update(id, pet);
3674
}
3775

src/api/controllers/UserController.ts

+54-2
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,94 @@
1+
import { Type } from 'class-transformer';
2+
import { IsEmail, IsNotEmpty, IsUUID, ValidateNested } from 'class-validator';
13
import {
24
Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, Req
35
} from 'routing-controllers';
6+
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
47

58
import { UserNotFoundError } from '../errors/UserNotFoundError';
69
import { User } from '../models/User';
710
import { UserService } from '../services/UserService';
11+
import { PetResponse } from './PetController';
12+
13+
class BaseUser {
14+
@IsNotEmpty()
15+
public firstName: string;
16+
17+
@IsNotEmpty()
18+
public lastName: string;
19+
20+
@IsEmail()
21+
@IsNotEmpty()
22+
public email: string;
23+
24+
@IsNotEmpty()
25+
public username: string;
26+
}
27+
28+
export class UserResponse extends BaseUser {
29+
@IsUUID()
30+
public id: string;
31+
32+
@ValidateNested({ each: true })
33+
@Type(() => PetResponse)
34+
public pets: PetResponse[];
35+
}
36+
37+
class CreateUserBody extends BaseUser {
38+
@IsNotEmpty()
39+
public password: string;
40+
}
841

942
@Authorized()
1043
@JsonController('/users')
44+
@OpenAPI({ security: [{ basicAuth: [] }] })
1145
export class UserController {
1246

1347
constructor(
1448
private userService: UserService
1549
) { }
1650

1751
@Get()
52+
@ResponseSchema(UserResponse, { isArray: true })
1853
public find(): Promise<User[]> {
1954
return this.userService.find();
2055
}
2156

2257
@Get('/me')
58+
@ResponseSchema(UserResponse, { isArray: true })
2359
public findMe(@Req() req: any): Promise<User[]> {
2460
return req.user;
2561
}
2662

2763
@Get('/:id')
2864
@OnUndefined(UserNotFoundError)
65+
@ResponseSchema(UserResponse)
2966
public one(@Param('id') id: string): Promise<User | undefined> {
3067
return this.userService.findOne(id);
3168
}
3269

3370
@Post()
34-
public create(@Body() user: User): Promise<User> {
71+
@ResponseSchema(UserResponse)
72+
public create(@Body() body: CreateUserBody): Promise<User> {
73+
const user = new User();
74+
user.email = body.email;
75+
user.firstName = body.firstName;
76+
user.lastName = body.lastName;
77+
user.password = body.password;
78+
user.username = body.username;
79+
3580
return this.userService.create(user);
3681
}
3782

3883
@Put('/:id')
39-
public update(@Param('id') id: string, @Body() user: User): Promise<User> {
84+
@ResponseSchema(UserResponse)
85+
public update(@Param('id') id: string, @Body() body: BaseUser): Promise<User> {
86+
const user = new User();
87+
user.email = body.email;
88+
user.firstName = body.firstName;
89+
user.lastName = body.lastName;
90+
user.username = body.username;
91+
4092
return this.userService.update(id, user);
4193
}
4294

0 commit comments

Comments
 (0)