UNPKG

10.6 kBMarkdownView Raw
1# Plumier
2Delightful Node.js Rest Framework
3
4[![Build Status](https://travis-ci.org/plumier/plumier.svg?branch=master)](https://travis-ci.org/plumier/plumier)
5[![Build status](https://ci.appveyor.com/api/projects/status/6carp7h4q50v4pj6?svg=true)](https://ci.appveyor.com/project/ktutnik/plumier-isghw)
6[![Coverage Status](https://coveralls.io/repos/github/plumier/plumier/badge.svg?branch=master)](https://coveralls.io/github/plumier/plumier?branch=master)
7[![Greenkeeper badge](https://badges.greenkeeper.io/plumier/plumier.svg)](https://greenkeeper.io/)
8[![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lernajs.io/)
9
10## Blog Posts and Publications
11
12* [Reason, motivation and how Plumier designed](https://medium.com/hackernoon/i-spent-a-year-to-reinvent-a-node-js-framework-b3b0b1602ad5)
13* [How to use Plumier with mongoose](https://hackernoon.com/create-secure-restful-api-with-plumier-and-mongoose-3ngz32lu)
14* [Advanced usage of Plumier middleware to perform AOP and metaprogramming](https://hackernoon.com/adding-an-auditing-system-into-a-rest-api-4fbb522240ea)
15
16## Tutorials
17
18* [Basic REST api tutorial using Knex.js](https://plumierjs.com/docs/tutorials/basic-sql/get-started)
19
20
21## Examples
22
23* [Basic REST API with Knex.js](https://github.com/plumier/tutorial-todo-sql-backend)
24* [Basic REST api with Mongoose](https://github.com/plumier/tutorial-todo-mongodb-backend)
25* [Plumier - React - Monorepo - Social Login](https://github.com/plumier/tutorial-monorepo-social-login)
26* [Plumier - React Native - Monorepo](https://github.com/plumier/tutorial-todo-monorepo-react-native)
27
28## Motivation
29
30Plumier primarily created for full stack developer who spend more time working on the UI side and focus on creating a good user experience. Plumier comes with some built-in production-ready features that make creating secure JSON Api fun and easy.
31
32### Lightweight
33Plumier relatively has small code base which make it light and fast. It uses Koa as its core http handler which is quite fast, below is comparison result of Koa, Plumier and Express.
34
35```
36GET method benchmark starting...
37
38Server Base Method Req/s Cost (%)
39plumier koa GET 33624.00 -0.06
40koa GET 33602.19 0.00
41express GET 17688.37 0.00
42nest express GET 16932.91 4.27
43loopback express GET 5174.61 70.75
44
45POST method benchmark starting...
46
47Server Base Method Req/s Cost (%)
48koa POST 12218.37 0.00
49plumier koa POST 11196.55 8.36
50express POST 9543.46 0.00
51nest express POST 6814.64 28.59
52loopback express POST 3108.91 67.42
53```
54
55Version 1.0.0-beta.9 successfully reduce the framework cost, its mean using Plumier is the same as using Koa + Koa Router + Joi stack with all of Plumier features.
56
57The benchmark script can be found [here](https://github.com/ktutnik/full-stack-benchmarks).
58
59### Flexible
60Almost every part of framework is fully configurable and easy to override. For example plumier route generation system provided flexibility using convention and also configuration.
61
62Plumier traverse through the controller directories and generate routes based on directory name, controller name, method name and parameter names. This behavior make you easily separate your controllers based on version etc.
63
64```typescript
65// path: controller/api/v1/users-controller.ts
66export class UsersController {
67
68 @route.put(":id")
69 modify(id:number, data:User){
70 //implementation
71 }
72}
73```
74
75Above class generated into
76
77```
78PUT /api/v1/users/:id
79```
80
81* `api` is a directory
82* `v1` is a directory
83* `user` is a controller `UsersController`
84* `:id` is method parameter, the method name is ignored
85
86Plumier has a flexible decorator based routing configuration, it makes you easily create clean restful api routes and nested restful api with separate controller.
87
88Check the [route cheat sheet](https://plumierjs.com/docs/refs/route) for detail information
89
90### Testable
91Plumier controller is a plain TypeScript class it doesn't need to inherit from any base class, thats make it easily instantiated outside the framework.
92
93Plumier provided powerful [parameter binding](https://plumierjs.com/docs/refs/parameter-binding) to bound specific value of request object into method's parameter which eliminate usage of Request stub. Controller returned object or promised object or throw `HttpStatusError` and translated into http response which eliminate usage of Response mock.
94
95```typescript
96export class AuthController {
97 @route.post()
98 login(userName:string, password:string){
99 const user = await userDb.findByEmail(email)
100 if (user && await bcrypt.compare(password, user.password)) {
101 return { token: sign({ userId: user.id, role: user.role }, config.jwtSecret) }
102 }
103 else
104 throw new HttpStatusError(403, "Invalid username or password")
105 }
106}
107```
108
109Controller above uses [name binding](https://plumierjs.com/docs/refs/parameter-binding#name-binding), `userName` and `password` parameter will automatically bound with request body `{ "userName": "abcd", "password": "12345" }` or url encoded form `userName=abcd&password=12345`.
110
111Testing above controller is as simple as testing plain object:
112
113```typescript
114it("Should return signed token if login successfully", async () => {
115 const controller = new AuthController()
116 const result = await controller.login("abcd", "12345")
117 expect(result).toBe(<signed token>)
118})
119
120it("Should reject if provided invalid username or password", async () => {
121 const controller = new AuthController()
122 expect(controller.login("abcd", "1234578"))
123 .rejects.toEqual(new HttpStatusError(403, "Invalid username or password"))
124})
125```
126
127### Secure
128Plumier provided built-in [type converter](https://plumierjs.com/docs/refs/converters), [validator](https://plumierjs.com/docs/refs/validation), [token based authentication](https://plumierjs.com/docs/refs/authorization), [declarative authorization](https://plumierjs.com/docs/refs/authorization#role-authorization) and [parameter authorization](https://plumierjs.com/docs/refs/authorization#parameter-authorization) which make creating secure JSON API trivial.
129
130```typescript
131@domain()
132export class User {
133 constructor(
134 @val.email()
135 public email: string,
136 public displayName: string,
137 public birthDate: Date,
138 @authorize.role("Admin")
139 public role: "Admin" | "User"
140 ) { }
141}
142```
143
144Above is `User` domain that will be used as controller parameter type. Its a plain TypeScript class using [parameter properties](https://www.typescriptlang.org/docs/handbook/classes.html#parameter-properties) decorated with some validation and parameter authorization.
145
146Plumier aware of TypeScript type annotation and will make sure user provided the correct data type, `@val.email()` will validate the email, `@authorize.role("Admin")` will make sure only Admin can set the role field.
147
148```typescript
149export class UsersController {
150 private readonly repo = new Repository<User>("User")
151
152 @authorize.role("Admin")
153 @route.get("")
154 all(offset: number, limit: number = 50) {
155 return this.repo.find(offset, limit)
156 }
157
158 @authorize.public()
159 @route.post("")
160 save(data: User) {
161 return this.repo.add(data)
162 }
163}
164```
165
166Above controller will generate routes below
167
168```
169POST /users
170GET /users?offset=0&limit=<optional>
171```
172
173Even if above controller implementation look so naive and vulnerable, but Plumier already done some security check before user input touching database. Get users route only accessible by Admin other user try accessing it will got 401 or 403 status. Save user is public so everyone can register to the service.
174
175Plumier done some data conversion and security check, example below is list of user input and their appropriate status returned.
176
177| User Input | Description |
178| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
179| `{ "email": "john.doe@gmail.com", "displayName": "John Doe", "birthDate": "1988-1-1" }` | Valid, `birthDate` converted to `Date` |
180| `{ "birthDate": "1988-1-1" }` | Invalid, `email` and `displayName` is required |
181| `{ "email": "abc", "displayName": "John Doe", "birthDate": "1988-1-1" }` | Invalid email |
182| `{ "email": "john.doe@gmail.com", "displayName": "John Doe", "birthDate": "abc" }` | Invalid `birthDate` |
183| `{ "email": "john.doe@gmail.com", "displayName": "John Doe", "birthDate": "1988-1-1", "hack": "lorem ipsum dolor sit amet" }` | Valid, `hack` field removed |
184| `{ "email": "john.doe@gmail.com", "displayName": "John Doe", "birthDate": "1988-1-1", "role" : "Admin" }` | Setting `role` only valid if login user is Admin |
185
186### Friendly
187Plumier enhanced with static route analysis which will print friendly message if you misconfigure controller or forgot some decorator.
188
189![static analysis](https://plumierjs.com/docs/assets/static-analysis.png)
190
191## Documentation
192Go to Plumier [documentation](https://plumierjs.com) for complete documentation and tutorial
193
194## Requirements
195* TypeScript
196* NodeJS >= 8.0.0
197* Visual Studio Code
198
199## Contributing
200To run Plumier project on local machine, some setup/app required
201
202### App requirements
203* Visual Studio Code (Recommended)
204* Nodejs 8+
205* Yarn `npm install -g yarn`
206
207### Local Setup
208* Fork and clone the project
209* Install dependencies by `yarn install`
210* Run test by `yarn test`
211
212### Debugging
213Plumier already provided vscode `task` and `launch` setting. To start debugging a test scenario:
214* Build the project
215* Locate the test file and narrow the test runs by using `.only`
216* Put breakpoint on any location you need on `.ts` file
217* On start/debug configuration select `Jest Current File` and start debugging
218* Process will halt properly on the `.ts` file.