express-ts-annotations
Advanced tools
Comparing version 1.0.3 to 2.0.0
{ | ||
"private": true, | ||
"type": "module", | ||
"license": "MIT" | ||
"type": "module" | ||
} |
{ | ||
"name": "express-ts-annotations", | ||
"version": "1.0.3", | ||
"version": "2.0.0", | ||
"description": "", | ||
@@ -19,4 +19,5 @@ "main": "./dist/cjs/index.js", | ||
"build": "rm -rf dist/ && npm run build:esm && npm run build:cjs", | ||
"build:esm": "tsc -p tsconfig.esm.json && mv moduleConfig.json dist/esm/package.json", | ||
"build:cjs": "tsc -p tsconfig.cjs.json" | ||
"build:esm": "tsc -p tsconfig.esm.json && cp esmConfig.json dist/esm/package.json", | ||
"build:cjs": "tsc -p tsconfig.cjs.json && cp cjsConfig.json dist/cjs/package.json", | ||
"test": "jest" | ||
}, | ||
@@ -34,4 +35,14 @@ "repository": { | ||
"devDependencies": { | ||
"@babel/preset-env": "^7.21.5", | ||
"@types/jest": "^29.5.1", | ||
"@types/supertest": "^2.0.12", | ||
"@typescript-eslint/eslint-plugin": "^5.59.1", | ||
"@typescript-eslint/parser": "^5.59.1", | ||
"babel-jest": "^29.5.0", | ||
"eslint": "^8.39.0", | ||
"jest": "^29.5.0", | ||
"supertest": "^6.3.3", | ||
"ts-jest": "^29.1.0", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.9.4" | ||
"typescript": "^5.0.4" | ||
}, | ||
@@ -38,0 +49,0 @@ "dependencies": { |
212
README.md
@@ -8,45 +8,168 @@ # express-ts-annotations | ||
## Usage | ||
Controller.js | ||
The easiest way of using the annotations is by explictily stating the routes and methods of the controller. | ||
```JavaScript | ||
import { BaseController, controller, route, middlewares } from "express-annotations"; | ||
... | ||
import { Request, Response } from "express"; | ||
import { controller, route } from "express-ts-annotations"; | ||
@controller("/orders") | ||
class OrderController extends BaseController { | ||
// Set the controller route to "/myRoute" | ||
@controller("/myRoute") | ||
export default class CustomController { | ||
// Create the GET route "/myRoute/helloWorld" | ||
@route("get", "/helloWorld") | ||
public helloMethod(_: Request, res: Response) { | ||
return res.status(200).json("Hello world!") | ||
} | ||
// This will generate the ALL route /orders/greeting | ||
@route("all", "greeting") | ||
public greeting(req: Request, res: Response) { | ||
... | ||
// Create the POST route "/myRoute/ping" | ||
@route("post", "/ping") | ||
public ping(req: Request, res: Response) { | ||
return res.status(200).json(req.body) | ||
} | ||
// This will generate the GET route /orders/my-orders and apply the middleware auth | ||
@route() // We can omit this decorator since we use the default behaviour and we use @middlewares below | ||
@middlewares([auth]) | ||
public getMyOrders(req: Request, res: Response) { | ||
... | ||
} | ||
``` | ||
Annotation parameters can also be omitted, which will make the system use the default behaviour. | ||
``` JavaScript | ||
import { Request, Response } from "express" | ||
import { controller, route } from "express-ts-annotations" | ||
// Set the controller route to "/greeting" | ||
@controller() | ||
export default class Greeting { | ||
private _greet = 'Hi!' | ||
// Create the GET route "/greeting/greet" | ||
@route() | ||
public getGreet(_: Request, res: Response) { | ||
return res.status(200).json(this._greet) | ||
} | ||
// This will generate the POST route /orders/ and apply the middleware auth | ||
@route("post", "/") | ||
@middlewares([auth]) | ||
public addNewOrder(req: Request, res: Response) { | ||
... | ||
// Create the POST route "/greeting/greet" | ||
@route() | ||
public postGreet(req: Request, res: Response) { | ||
this._greet = req.body.msg | ||
return res.status(200).json(`Changed greet message to ${JSON.stringify(this._greet)}!`) | ||
} | ||
// This will generate the DELETE route /orders/all-my-orders and apply the middleware auth | ||
@route("delete") | ||
@middlewares([auth]) | ||
public allMyOrders(req: Request, res: Response) { | ||
... | ||
} | ||
``` | ||
It's also possible to specify just some of the parameters and have the rest with the default behaviour. | ||
``` JavaScript | ||
import { Request, Response } from "express"; | ||
import { controller, route } from "express-ts-annotations"; | ||
// Set the controller route to "/mixed" | ||
@controller("mixed") | ||
export default class MixedController { | ||
// Create the GET route "/mixed/getter" | ||
@route("get") | ||
public getter(_: Request, res: Response) { | ||
return res.status(200).json("Some response") | ||
} | ||
} | ||
``` | ||
### Middlewares | ||
Middlewares can be easily be set up using the `@middlewares()` annotation to which we pass the array of middlewares. | ||
export default new OrderController(); | ||
``` JavaScript | ||
import { NextFunction, Request, Response } from "express"; | ||
import { controller, middlewares, route } from "express-ts-annotations" | ||
function mid1(req: Request, res: Response, next: NextFunction) { | ||
console.log("Middleware 1 called"); | ||
next(); | ||
} | ||
function mid2(req: Request, res: Response, next: NextFunction) { | ||
console.log("Middleware 2 called"); | ||
next(); | ||
} | ||
// Set the controller route to "/middlewares" | ||
@controller() | ||
export default class Middlewares { | ||
// Create the GET route "/middlewares/both-middlewares" | ||
// Call middlewares "mid1" and "mid2" before calling the controller method | ||
@middlewares([mid1, mid2]) | ||
public getBothMidds(req: Request, res: Response) { | ||
return res.status(200).json("Mid1-Mid2") | ||
} | ||
// Create the GET route "/middlewares/mid2" | ||
@route("get", "mid2") | ||
// Call the middleware "mid2" before calling the controller method | ||
@middlewares([mid2]) | ||
public someMethod(req: Request, res: Response) { | ||
return res.status(200).json("Mid2") | ||
} | ||
} | ||
``` | ||
### Error handling | ||
The system also offers the posibility to create a controller-wide error handler. Which will be used to handle thrown errors from all the route controller methods and will again be able to be defined explictily by using the `@errorHandler` annotation | ||
index.js | ||
``` JavaScript | ||
import { NextFunction, Request, Response } from "express"; | ||
import { ControllerFunction, controller, errorHandler, route } from "express-ts-annotations"; | ||
// Set the controller route to "/errors" | ||
@controller("errors") | ||
export default class ErrorHandler { | ||
// Create the GET route "/errors/message" | ||
@route("get", "message") | ||
public errorThrower(req: Request, res: Response) { | ||
throw new Error("Bad credentials for access!") | ||
} | ||
// Set the controller error handler method to the method below | ||
@errorHandler | ||
public async handler(fn: ControllerFunction, req: Request, res: Response, next: NextFunction) { | ||
try { | ||
await fn(req, res, next); | ||
} catch(e) { | ||
if((e as Error).message.includes("Bad credentials")) { | ||
return res.status(401).json("Unauthorized!") | ||
} | ||
else { | ||
return res.status(500).json("Server error!") | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
or implicitly by having a class method named `errorHandler`. | ||
``` JavaScript | ||
import { NextFunction, Request, Response } from "express"; | ||
import { ControllerFunction, controller, route } from "express-ts-annotations" | ||
// Set the controller route to "/implicit-error-handler" | ||
@controller() | ||
export default class ImplicitErrorHandler { | ||
private _errorMsg = "Error msg!" | ||
// Creathe the GET route "/implicit-error-handler/error-message" | ||
@route() | ||
public getErrorMessage(req: Request, res: Response) { | ||
throw new Error() | ||
} | ||
// Set the controller error handler method to the method below | ||
public async errorHandler(fn: ControllerFunction, req: Request, res: Response, next: NextFunction) { | ||
try { | ||
await fn(req, res, next); | ||
} catch(e) { | ||
return res.status(500).json(this._errorMsg) | ||
} | ||
} | ||
} | ||
``` | ||
And for setting up the server we simply inject an instance of each controller in our app using the `injectControllers` function. | ||
```JavaScript | ||
import { injectControllers } from "express-annotations"; | ||
import { injectControllers } from "express-ts-annotations"; | ||
import Controller1 from "./C1" | ||
import Controller2 from "./C1" | ||
... | ||
@@ -56,24 +179,27 @@ | ||
const con1 = new Controller1(); | ||
const con2 = new Controller2(); | ||
... | ||
injectControllers(app, [orderController]); | ||
injectControllers(app, [con1, con2]); | ||
``` | ||
## Classes | ||
### `BaseController` | ||
This is a class that all controllers must extend. It contains some internal information used by the system. | ||
## Annotations | ||
### `@controller(path: string)` | ||
A class decorator that sets up the `path` in which the controller will be injected. If the `path` doesn't start with a `/`, it will be automatically added by the system. The default `path` is `/`. | ||
### `@controller(path?: string)` | ||
A class decorator that sets up the `path` in which the controller will be injected. If the `path` doesn't start with a `/`, it will be automatically added by the system. The default `path` is the name of the class. | ||
### `@route(method?: RESTMethod, path?: string)` | ||
This is a method decorator that assigns the method to a `path`. | ||
By default if no parameters are provided the system will analyze the method name and assign a rest `method` and `path` automatically, | ||
for this the method name must start with a RESTMethod (i.e `get`, `post`, `put`, `patch`, `delete`, `all`) followed by the path written in CamelCase | ||
which will be transformed automatically in kebab case. If only the `method` is specified the name will still be asigned automatically. If the `method` or `path` is specified, they will be used instead of the system generated ones. | ||
This is a method decorator that assigns the controller method to a `path`. | ||
By default if no parameters are provided the system will analyze the controller method name and assign a rest `method` and `path` automatically, | ||
for this the method name must start with a case insensitive RESTMethod (i.e `get`, `post`, `put`, `patch`, `delete`, `all`) followed by the path written in CamelCase which will be transformed automatically in kebab case. If only the `method` is specified the name will still be asigned automatically. If the `method` or `path` is specified, they will be used instead of the system generated ones. | ||
If the `path` doesn't start with `/`, it will be added automatically. | ||
### `@middlewares(middlewares: Middleware[])` | ||
This method decorator is used to specify the `middlewares` that should apply to the `route` of the method. | ||
It can be used without `@route()` decorator, in which case the default behaviour of `@route()` specified above will apply. | ||
This method decorator is used to specify the `middlewares` that should apply to the `route` of the controller method. It can be used without `@route()` decorator, in which case the default behaviour of `@route()` specified above will apply. | ||
### `@errorHandler` | ||
This method decorator will asign the controller error handling function. This can be omitted if the method name is `errorHandler`. If both a method with the name `errorHandler` and a method wit the `@errorHandler` annotation exist in the class, the annotated method will be used. | ||
## Functions | ||
### `injectControllers(app: Express, controllers: BaseController[])` | ||
### `injectControllers(app: Express, controllers: any[])` | ||
This function is used to inject the `controllers` into the `express` application. | ||
## Disclaimer | ||
- Version `2.0.0^` has migrated to Typescript `5.0` decorators as opposed to the `experimentalDecorators` from previous versions. Also I removed the `BaseController` class from previous versions. | ||
- The decorated class methods must be public in order to bind the class context to them. | ||
- The types are a bit loose, feel free to improve them and send PR requests on github. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
61056
89
658
204
12
1