UNPKG

11.7 kBMarkdownView Raw
1# lynx
2
3lynx is a NodeJS framework for Web Development, based on decorators and the async/await support.
4
5The idea is to enforce code maintainability and readability enforcing a precise project structure, **decorators** and completely remove the callback (or promises) nightmare leaning on the new **async/await** supports, available in the new versions of NodeJS.
6
7Lynx is influenced by the Java Spring framework, trying to bring a more enterprise-level environment to NodeJS.
8
9## Libraries
10
11Lynx is founded by state-of-the-art libraries. It uses:
12
13- **[ExpressJS](http://expressjs.com/)** for the management of routes;
14- **[nunjucks](https://mozilla.github.io/nunjucks/)** as template engine;
15- **[TypeORM](http://typeorm.io/)** as ORM to the database;
16- **[GraphQL](http://graphql.org/)** to automatically exposes an API to the database entities;
17- **[JWT](https://jwt.io/)** to enable token authentication;
18- **[multer](https://github.com/expressjs/multer)** to manage file upload;
19- **[nodemailer](https://nodemailer.com)** to send emails;
20- **[joi](https://github.com/hapijs/joi)** to validate the requests;
21- **[sharp](http://sharp.dimens.io/)** to perform image resizing and other operations.
22
23## Out-Of-The-Box Features
24
25With Lynx, you will have the following functionality out of the box:
26
27- user management, and low level function for login, registration, authorization and authentication;
28- media upload management, file upload and retrieving, on a virtual-folder environment;
29- multi-language is a first class citizen! You can use new nunjucks filter to enable multi-language on the template engine, but also during fields validation!
30- GraphQL (queries and mutations!) automatically generated based on your Database entities and decoration.
31- datatables, directly integrated on the template engine, with pagination, filtering and ordering.
32
33## Installation
34
35```
36npm install lynx-framework
37```
38
39## Lynx application structure
40
41A Lynx application shall be formed by different folders:
42
43```
44.
45├── controllers
46│   ├── backoffice
47│   │   └── main.controller.ts
48│   └── main.controller.ts
49├── entities
50├── index.ts
51├── libs
52├── locale
53│   ├── en.json
54│   └── it.json
55├── middlewares
56│   └── always.middleware.ts
57├── public
58└── views
59 └── main.njk
60```
61
62- The `controllers` folder shall contain (with subfolder support) any controllers.
63- The `entities` folder shall contain any entities, that will be automatically mapped with `TypeORM`.
64- The `libs` folder shall contain any additional libraries or utility functions, that can be used by controllers and middlewares.
65- The `local` folder shall contains localization file formatted as key-value JSON file.
66- The `middlewares` folder shall contain (with subfolder support) any middleware.
67- The `public` folder shall contain all the public resources, such as images, css and so on.
68- The `view` folder shall contain the nunjucks templates. An `emails` subfolder, containing the email templates, is recommended.
69
70The project structure can be customized.
71
72## Lynx application
73
74To start a Lynx application, it is necessary to instantiate a Lynx `App` object. For example, the `index.ts` file can be:
75
76```
77import { App, ConfigBuilder } from "lynx-framework/app";
78
79const port = Number(process.env.PORT) || 3000;
80
81const app = new App(new ConfigBuilder(__dirname).build());
82app.startServer(port);
83```
84
85Any Lynx configuration, such as database connection, token secrets, folders and so on, can be customized using the `ConfigBuilder` object.
86Any controllers, middlewares and entities will be automatically loaded by the Lynx app.
87
88## Controllers
89
90A controller defines a set of endpoints. Any endpoint responds to a specific
91path and HTTP verb, and can generate an HTML or JSON response.
92Any controller shall extends the `BaseController` class, in order to inherit a lot of
93utility methods.
94It is possible to define only ONE controller for each file, and the class shall be `export default`.
95Moreover, the file should be named as `controllerName.controller.ts`.
96
97The minimum configuration of a controller is the following:
98
99```
100import { Route } from "lynx-framework/decorators";
101import BaseController from "lynx-framework/base.controller";
102
103@Route("/myController/path")
104export default class MyController extends BaseController {
105 ...
106}
107```
108
109To define endpoints, it is necessary to decor a method. There is a decorator for each HTTP verb (GET, POST, etc..). For example:
110
111```
112 ...
113 @GET('/helloWorld')
114 async helloWorld() {
115 return "Hello, world!";
116 }
117```
118
119the method `helloWorld` will be executed for any `GET` request to `/myController/path/helloWorld`.
120
121### Method decorators
122
123#### `GET(path)`, `POST(path)`, `PUT(path)`, `PATCH(path)`, `DELETE(path)`
124
125These decorators map the method to the chosen HTTP verb with the specified path.
126Moreover, `path` can contains path parameters, for example `/authors/:id/posts`. The path parameters will be injected in the method as arguments:
127
128```
129 @GET('/authors/:id/posts/:secondParameter')
130 async doubleParameters(id:Number, secondParameter:String) {
131 ...
132 }
133```
134
135Since Lynx is based on Express, more information about the url parameters can be found [here](https://expressjs.com/en/guide/routing.html).
136
137**IMPORTANT** these decorators shall be put just before the method definition, and as last decorator used to the method.
138
139### `API()`
140
141The `API` decorator enforce the serialization of the returned object to JSON. This feature is very useful to build an API.
142In this case, the returned object will be added inside the following standard return object:
143
144```
145 {
146 "success": true/false,
147 "data": "returned object serialized"
148 }
149```
150
151If the method returns a boolean value, the return object will be:
152
153```
154 {
155 "success": returned value
156 }
157```
158
159For more information about the object serialization, please check the [Entity chapter]().
160
161### `MultipartForm()`
162
163This decorator simply allows the MultipartForm in post request. It is essential to enable the automatic file upload system.
164
165### `Name(name)`
166
167This decorator allows to set a name to the route. So, it is possible to recall this route in a very simple way.
168The route name is used by any `route` functions.
169
170### `Verify(function)`
171
172Add to the decorated method a verification function that will be executed BEFORE the route.
173The function must NOT be an async function, and it shell return a boolean value. If true is returned, the method is then executed. This method is fundamental to implement authorization to a single endpoint.
174NOTE: the function shall NOT be a class method, but a proper Typescript function.
175Example:
176
177```
178function alwaysDeny(req, res) {
179 return false;
180}
181...
182@Verify(alwaysDeny)
183@GET("/unreachable")
184async someMethod() {
185 ...
186}
187```
188
189### `AsyncVerify(function)`
190
191Add to the decorated method a verification function that will be executed BEFORE the route.
192The function MUST BE an async function, and it shell return a boolean value. If true is returned, the method is then executed. This method is fundamental to implement authorization to a single endpoint.
193NOTE: the function shall NOT be a class method, but a proper Typescript function.
194
195> This method is available from version 0.5.5
196
197Example:
198
199```
200async function alwaysDeny(req, res) {
201 return false;
202}
203...
204@AsyncVerify(alwaysDeny)
205@GET("/unreachable")
206async someMethod() {
207 ...
208}
209```
210
211### `Body(name, schema)`
212
213The `Body` decorator inject the request body as a parameter of the decorated method. The body object
214is automatically wrapped inside a `ValidateObject`, that is verified using a [JOI schema](https://github.com/hapijs/joi).
215Example:
216
217```
218import { ValidateObject } from "lynx-framework/validate-object";
219import * as Joi from "joi";
220
221const loginSchema = Joi.object().keys({
222 email: Joi.string()
223 .email()
224 .required()
225 .label("{{input_email}}"), //I can use a localized string!
226 password: Joi.string()
227 .required()
228 .min(4)
229 .regex(/^[a-zA-Z0-9]{3,30}$/)
230 .label("{{input_password}}") //I can use a localized string!
231});
232
233...
234
235@Body("d", loginSchema)
236@POST("/login")
237async performLogin(
238 d: ValidateObject<{ email: string; password: string }>
239) {
240 if (!d.isValid) {
241 //d.errors contains localized errors!
242 return false;
243 }
244 let unwrapped = d.obj; //I can use unwrapped.email and unwrapped.password!
245 ...
246}
247```
248
249Starting from version `0.5.8`, a new builder class can be used to create Joi schemas.
250The previous example can be simplified as follows:
251
252```
253import { ValidateObject, SchemaBuilder } from "lynx-framework/validate-object";
254
255...
256
257const loginSchema = new SchemaBuilder()
258 .email("email")
259 .withLabel("{{input_email}}")
260 .password("password")
261 .withLabel("{{input_password}}")
262 .build();
263
264...
265
266@Body("d", loginSchema)
267@POST("/login")
268async performLogin(
269 d: ValidateObject<{ email: string; password: string }>
270) {
271 if (!d.isValid) {
272 //d.errors contains localized errors!
273 return false;
274 }
275 let unwrapped = d.obj; //I can use unwrapped.email and unwrapped.password!
276 ...
277}
278```
279
280### Advanced
281
282#### Accessing the original `req` and `res`
283
284When an endpoint method is called, the last two arguments always are the original `req` and `res` objects.
285The `req` object has also the `user` and `files` properties (it is a _Lynx Request Object_).
286The use of `res` object is discouraged, in favor of a standard returned object from the endpoint method.
287Example:
288
289```
290@GET("/endpoint/:id")
291async myEndpoint(id:Number, req: Request, res: Response) {
292 ...
293}
294```
295
296#### `postConstructor`
297
298It is possible to override the `postConstructor` methods that will be called after the creation of the controller. This method is `async`, so it is possible to perform asynchronous initialization. Always remember that there will be only ONE instance of any controller in a Lynx application.
299
300## Enhancements to the Nunjucks engine
301
302### `tr` filter
303
304The `tr` filter automatically localize a string. Usage:
305
306```
307<button type="submit" class="btn btn-primary px-4">{{ "button_login" | tr }}</button>
308```
309
310The `button_login` shall be a property in the JSON localized file.
311
312### `json` filter
313
314The `json` filter automatically format an object or variable to JSON.
315
316### `format` filter
317
318The `format` filter format a number to a string, with a fixed number of decimal digits (default: 2).
319Usage:
320
321```
322<span class="price">€ {{ price | format }}</span>
323<span class="integer_number">{{ myNumber | format(0) }}
324```
325
326### `date` filter
327
328The `date` filter format a date to a string, using the `moment`. The default format will use the
329`lll` format, but it is possible to override this behavior.
330Usage:
331
332```
333<span class="date">€ {{ data.createdAt | date }}</span>
334<span class="my_date_custom">{{ data.createdAt | date("YYYY-MM-DD") }}
335```
336
337### `route` global function
338
339The `route` function compile a route name to an url with the given parameters.
340If an url is used instead of a route name, the url is still compiled with the given parameters.
341Usage:
342
343```
344<a href="{{route('forgot_password')}}" class="btn btn-link px-0">...</a>
345```
346
347To set the name of a route, use the `Name` decorated to the chosen method.
348
349### `old` global function
350
351The `old` function is used to retrieve the latest value of a form. It can be used to retrieve the value of an input while performing server side form validation. It is also possible to specify a default value.
352Usage:
353
354```
355<input type="email" name="email" class="form-control" value="{{old('email')}}">
356```