A powerful Typescript based middleware for Express.
- Installation
- Goal and Philosophy
- Getting Started
- Documentation
- Controllers
- Routing
- Request Parameters
- Request Deserialization
- Complex Models
- Input Validation
- Response Entity
- Project Hierarchy
- The Bootstrap
- Authentication & Authorization
- Dependency Injection - CLI
- People
- License
This is a Node.js module available through the npm registry.
Installation is done using the npm install
command:
$ npm install xpress-bootstrap
Express Bootstrap
strives to be a powerful middleware for Express.js that makes developing APIs faster and easier.
Note: Navigate to CLI section to learn about quickly generating a Getting Started project.
- Install
xpress-bootstrap
.$ npm install xpress-bootstrap
- Install Express.
$ npm install express
- Install ts-node, typescript and @types/express as dev dependencies.
$ npm install -D ts-node typescript @types/express
- Run the following command to initiate a
typescript
project:A$ npx tsc --init
tsconfig.json
file will be generated. - In tsconfig.json, change the
target
to ES6 or later and uncommentexperimentalDecorators
andemitDecoratorMetadata
. - Create a folder named
controllers
in the root directory of the project. - Create a file
homeController.ts
inside thecontrollers
folder and copy the following snippet:import { HTTPResponse } from "xpress-bootstrap/bin/constants/enum"; import { BaseController } from "xpress-bootstrap/bin/controllers/baseController"; import { Get } from 'xpress-bootstrap/bin/decorators/routingDecorator'; import { Response } from 'xpress-bootstrap/bin/models/response'; import { Controller } from 'xpress-bootstrap/bin/decorators/controllerDecorator'; @Controller export class HomeController extends BaseController { @Get('/hello') index() { const resp = { Message: 'Hello World of Xpress Bootstrap!', Controller: 'HomeController' }; return new Response(HTTPResponse.Success, resp); } }
- Create a file
app.ts
and copy the following snippet:import { bootstrap } from "xpress-bootstrap/bin/bootstrapper"; import express from 'express'; const app = express(); app.use(bootstrap({ base: '/api', // Optional base url for all routes })); const port = 5000; app.listen(port, () => console.log(`Listening at port ${port}`));
- Use
npx ts-node app.ts
to run the application. - Navigate to localhost:5000 in your browser.
-
API Controllers are classes that group together a set of endpoints that lie under the same resource. For instance, all endpoints/api actions relating to a user could be grouped under a
UsersController
.To create an api controller, simply export a typescript class that extends the
BaseController
class. The class should be decorated with@Controller
and its name should end inController
.All controllers must extend the
BaseController
, otherwise they won't be bootstrap compatible.@Controller export class UsersController extends BaseController {}
-
-
Specifies that a controller method is to be exposed as an HTTP GET endpoint. Provide an optional route for the method to be exposed as endpoint. If not provided, assumes the default for this resource.
@Get('/hello')
-
Specifies that a controller method is to be exposed as an HTTP POST endpoint. Provide an optional route for the method to be exposed as endpoint. If not provided, assumes the default for this resource.
@Post()
-
Specifies that a controller method is to be exposed as an HTTP PUT endpoint. Provide an optional route for the method to be exposed as endpoint. If not provided, assumes the default for this resource.
@Put('/:id/:username')
-
Specifies that a controller method is to be exposed as an HTTP PATCH endpoint. Provide an optional route for the method to be exposed as endpoint. If not provided, assumes the default for this resource.
@Patch()
-
Specifies that a controller method is to be exposed as an HTTP DELETE endpoint. Provide an optional route for the method to be exposed as endpoint. If not provided, assumes the default for this resource.
@Delete('/:id')
-
Specifies that a controller method is to be exposed as an HTTP GET endpoint. Provide an optional route for the method to be exposed as endpoint. If not provided, assumes the default for this resource.
-
-
Route parameters are defined in the route. You do this by placing a
:
and providing a name. For instance:There can be multiple route parameters in a single route.'api/users/:userId'
-
Query parameters come from the query section of the request. The query section of a request begins after the
?
. For instance:The query section can be deserialized into a typescript class using the decorators described in the section'api/users?page=5&limit=10'
Request Deserialization
below. -
Body parameters come from the body/payload of the request. These can be any random JSON. For instance:
The payload section can be deserialized into a typescript class using the decorators described in the section
{ "name": "John Doe", "age": 24, "gender": "M" }
Request Deserialization
below.
-
Route parameters are defined in the route. You do this by placing a
-
Express Bootstrap
can also deserialize api requests into specified custom models.-
Use the
FromRoute
decorator to parse a parameter from the route. The usage is as below:@Get('/:id') getUserById(@FromRoute('id') id: number)
Note that the parameter name in the route and in the
FromRoute
decorator should match. -
Use the
FromQuery
decorator to parse the whole query section into a custom typescript class/model.To do this, first define a model, let's say,
UserFilter
.// userFilter.ts export class UserFilter extends Model { page: number; limit: number; }
*Note that the models that you want
Express Bootstrap
to auto deserialize should extend theModel
class.Next, use the
FromQuery
decorator in the method parameter and provide theUserFilter
model as a parameter to the decorator, as follows:getUsersByFilter(@FromQuery(UserFilter) userFilter: UserFilter)
-
Use the
FromBody
decorator to parse the whole body/payload section into a custom typescript class/model.To do this, first define a model, let's say,
User
.// user.ts export class User extends Model { public name: string; public email: string; public age: number; }
*Note that the models that you want
Express Bootstrap
to auto deserialize should extend theModel
class.Next, use the
FromBody
decorator in the method parameter and provide theUser
model as a parameter to the decorator, as follows:createUser(@FromBody(User) user: User)
-
-
Sometimes you want your models to be a bit complex. This could include cases like composition. This is where the
Compose
andComposeMany
decorators come into play.Compose: is used when a type composes of another type. For instance, let us assume that the user has an address, which is a separate type. The user model would now look like the following:
export class User extends Model { public name: string; public email: string; public age: number; @Compose(Address) public address: Address; }
The
Address
could in turn compose of other types.ComposeMany: is used when a type composes of a collection of another type. For instance, let us assume that the user has a set of nicknames. For brevity, the nicknames are just simple strings. The user model would now look like the following:
export class User extends Model { public name: string; public email: string; public age: number; @ComposeMany(String) public nicknames: string[]; }
Note: The limitation of these decorators while deserialization is because of the fact that typescript transpiles into javascript, hence all the types are dissolved at runtime. Since no type information is available at runtime, these decorators are needed to provide the necessary information for proper deserialization.
-
Express Bootstrap
uses Joi for its user input validation. TheValidate
decorator is used to validate each individual field of a model.The validate decorator takes an object that has the following 3 properties:
constraint: TheJoi
constraint for this property.
arrayElementsConstraint (Optional): If the property is an array of object, this property should be provided to specify the constraint on the array objects.
model (Optional): If an object is used, the type should be provided. The object class should extend the 'Model' class.Lets take our good old
User
type and apply input validation to it. It would look something like the following:export class User extends Model { @Validate({ constraint: Joi.string() }) public name: string; @Validate({ constraint: Joi.string().email() }) public email: string; @Validate({ constraint: Joi.number().min(18).max(60) }) public age: number; @Validate({ constraint: Joi.array().items().required(), model: Address, arrayElementsConstraint: Joi.object().required() }) @Compose(Address) public address: Address; @Validate({ constraint: Joi.array().items(Joi.string()).required() }) @ComposeMany(String) public nicknames: string[]; }
name is a string.
email is a string that is an email.
age is a number whose value can be between 18 and 60 inclusive.
address is an array that is required. It is of typeAddress
. The array should have atleast one object.
nicknames is an array of strings. The array is required.For a full list of validation options and capabilities, kindly check Joi documentation.
-
A
Response
entity could be used to return a status code alongwith the response object. The usage is as follows:import { HTTPResponse } from 'xpress-bootstrap/bin/constants/enum'; import { Response } from 'xpress-bootstrap/bin/models/response'; ... ... @Get() index() { return new Response(HTTPResponse.Success, { message: 'Hello World of Express Bootstrap' }); }
-
As you may have noticed, in the Getting Started section, we had placed the
homeController.ts
file inside a folder namedcontrollers
. This is indeed a requirement for Express Bootstrap. By default, all the controllers should be placed in a directory named controllers, which is read during the bootstrap process and all the qualifying controllers are registered with the application. You can, however, change the path of the controllers directory. As a general convention, this has been made part of the configuration. For this, create a JSON file namedbootstrapperConfig.json
in your project root and save the following inside the file:{ "controllersDir": "./handlers" }
Express Bootstrap will now look into a directory named
handlers
to find the controllers to be registered. Since Express Bootstrap iterates the controllers folder recursively searching for all qualifying controllers, we recommend the following:- DONOT keep other files within the controllers directory, unless absolutely necessary.
- Try to keep the directory depth as minimal as possible.
- DONOT place controllers on the project root.
-
Since
Express Bootstrap
is desgined as a middleware forExpressJS
, it should be registered with Express to be used. Registration is straightforward and can be done in few simple steps.bootstrap
is a function so it should first be imported:import { bootstrap } from "xpress-bootstrap/bin/bootstrapper";
After importing
bootstrap
,express
, and other dependencies, its time to setup express. Let us create a simple express application with not much going on. This could be done as follows:const app = express();
Now, register
bootstrap
as a middleware to express by providing the required parameters:app.use(bootstrap({ base: '/api' }));
base: It is an optional base url for all routes. For example, /api, /api/v1...
Note that
bootstrap
should be the last middleware used since its responsible for terminating the api request. -
Express Bootstrap provides the capability to authenticate and authorize api calls. Since a user may wish to implement auth one way or the other, Express Bootstrap leaves the implementaion upto the user. Auth can be enabled by providing an optional callback to the
bootstrap
function. This callback is called before executing the auth-enabled endpoints' logic. The callback is a function that takes two arguments, aRequest
and aEndpoint
entity, and returns aAuthResponse
.(req: Request, endpoint: Endpoint) => AuthResponse
Request is a class wrapping headers, query, and body of an API Request.
Endpoint is a class that contains basic information of the Endpoint that will be called.
AuthResponse is a class representing response of authentication and authorization.
A typical callback function would look like the following:
const authCallback = (req: Request, endpoint: Endpoint) => { // Logging contents of Request console.log(req); // Logging contents of Endpoint console.log(endpoint); // The auth failed response return new AuthResponse(false, HTTPResponse.Unauthorized, 'This is a test unauthorization message'); }
Now the question arises, how does
Express Bootstrap
determine which endpoints would be under auth. For this purpose, three different decorators are used to achieve different results:Used on an endpoint/controller method to enable auth on that particular endpoint.
Used on a controller to enable auth on all endpoints/methods belonging to that controller.
Used to exclude an endpoint from auth. It is useful in cases where the
@ControllerAuth
is used and you wish to exclude an endpoint or two.The
@Auth
and@ControllerAuth
decorators also take an optional parameter, the auth callback like thebootstrap
method.Method/endpoint level auth callback >> controller level auth callback >> Bootstrap function's auth callback.
-
Plain old controllers don't seem to provide much fun. Not to mention you can't have all the business logic inside the controller itself. Generally, you may want to abstract out the business logic to the service layer. This makes a controller dependent upon the relevant service(s). This is where Dependency Injection(DI) comes into play. You can inject any number of dependencies (we call them services) into the controllers through their constructors. All that is needed is for Express Bootstrap to know which dependency to inject, in other words, a dependency should be registered for it to be injected properly. For this purpose, the
@Injectable
decorator is used. Let's take an example of a controller namedRandomController
that has a methodindex()
which returns a random number. The controller would look something like the following:// Imports @Controller export class RandomController extends BaseController { @Get() index() { return Math.round(Math.random() * 100); } }
Seems pretty cool, right? Now imagine that at some point in future, your logic to fetch the random number becomes too complicated and you want to abstract it into a class named
RandomService
. You can create your class and decorate it with the@Injectable
decorator, like the following:import { Injectable } from "xpress-bootstrap/bin/decorators/iocDecorator"; @Injectable('randomService') export class RandomService { getRandomNumber() { // Complex logic here... } }
What this would do is register the
RandomService
as randomService (name passed as parameter to @Injectable) inside the IoC container of Express Bootstrap. You can now injectRandomService
as a dependency inside theRandomController
constructor:@Controller export class RandomController extends BaseController { constructor(private randomService: RandomService) { super(); } @Get() index() { return this.randomService.getRandomNumber(); } }
Note that the dependency parameter name in the constructor should be the same as that of the registration name you passed in
@Injectable
. As long as Express Bootstrap is resolving the dependency, you can inject any class anywhere in the application.
Express Bootstrap
also comes with a CLI to easily initiate a Getting Started project.
After initiating an npm project and installing xpress-bootstrap
, use the following:
npx xpress-bootstrap init
This will initiate a typescript project, create a basic application, and install the required dependencies.
The original author of Express Bootstrap is Shuja Rizvi.