mvc-middleware
Mvc middleware for express like .Net Mvc
Installation
npm i mvc-middleware
How to use
// src/index.ts
import { json } from 'body-parser';
import cors from 'cors';
import express from 'express';
import http from 'http';
import { MvcMiddleware } from 'mvc-middleware';
import path from 'path';
// Path to folder where you have your controllers.
// Middleware will search controllers recursively.
// Each file with '.ts' extension and default export and
// typeof === 'function', decorated with '@api' will be
// assumed as controller class.
const controllersPath = path.join(__dirname, 'api');
const expressApp = express();
expressApp.use(cors()).use(json());
const mvcMiddleware = new MvcMiddleware(expressApp);
(async () => {
await mvcMiddleware.registerControllers(controllersPath);
http
.createServer(expressApp)
.listen(80, 'localhost');
})();
// src/api/UsersApi.ts
import { MvcController } from 'mvc-middleware';
@api('/api')
export default class UsersApi extends MvcController {
@GET // api/users
users() {
return this.ok(['user-1', 'user-2', 'user-3']);
}
}
// src/api/ArticlesApi.ts
import { MvcController } from 'mvc-middleware';
@api('/api')
export default class ArticlesApi extends MvcController {
@GET // articles
async articles() {
const articles = await Promise.resolve(['article-1', 'article-2']);
return this.ok(articles);
}
}
Dependency injection
We recommend to use cheap-di package to handle your dependency injection:
import { container } from 'cheap-di';
import express from 'express';
const expressApp = express();
const mvcMiddleware = new MvcMiddleware(expressApp, container);
// ...
You may pass any custom DI resolver that implements the following contract:
type AbstractConstructor<T = any> = abstract new (...args: any[]) => T;
type Constructor<T = any> = new (...args: any[]) => T;
type Resolver = <TInstance>(type: Constructor<TInstance> | AbstractConstructor<TInstance>, ...args: any[]) => TInstance | undefined;
interface DependencyResolver {
/** instantiate by class */
resolve: Resolver;
}
Decorators
We have two sets of decorators: for stage 2 (legacy) and stage 3 proposals. You may choose one of them with imports:
import { api, GET, POST, PUT, PATCH, DELETE } from 'mvc-middleware/stage2';
import { api, GET, POST, PUT, PATCH, DELETE } from 'mvc-middleware/stage3';
decorator | description | variants of using |
---|---|---|
api | collect method names and types (get/post/...) and concat prefix to is urls |
@api @api('api') @api('/api/domain')
|
GET | marks method as handler for GET request, can change handled url |
@GET @GET('') @GET('my-route') @GET('/my-route')
|
POST | marks method as handler for GET request, can change handled url |
@POST @POST('') @POST('my-route') @POST('/my-route')
|
PUT | marks method as handler for PUT request, can change handled url |
@PUT @PUT('') @PUT('my-route') @PUT('/my-route')
|
PATCH | marks method as handler for PATCH request, can change handled url |
@PATCH @PATCH('') @PATCH('my-route') @PATCH('/my-route')
|
DELETE | marks method as handler for DELETE request, can change handled url |
@DELETE @DELETE('') @DELETE('my-route') @DELETE('/my-route')
|
MvcController methods
Method name | Response status code | Response type | Arguments | Description |
---|---|---|---|---|
ok |
200 | text or json | model?: any |
returns 200 status code with data |
created |
201 | text or json | model?: any |
returns 201 status code with data |
accepted |
202 | text or json | model?: any |
returns 202 status code with data |
noContent |
204 | - | - | returns 204 status code |
found |
302 | text | url: string |
returns 302 status code |
permanentRedirect |
308 | text | url: string |
returns 308 status code |
redirect |
300 - 308 | text | statusCode: number, url: string |
returns redirection status code |
badRequest |
400 | text or json | model?: any |
returns 400 status code with data |
unauthorized |
401 | text or json | model?: any |
returns 401 status code with data |
forbid |
403 | - | model?: any |
returns 403 status code |
notFound |
404 | text or json | model?: any |
returns 404 status code with data |
conflict |
409 | text or json | model?: any |
returns 409 status code with data |
serverError |
500 | text | message?: any |
returns 500 status code with error message |
sendResponse |
any http status code | text | model: any, statusCode?: number |
returns status code with data. Default status code is 200 |
More examples
// src/api/UsersController.ts
import { Request, Response } from 'express';
import { api, GET, POST } from 'mvc-middleware/stage2';
@api('/api/users')
export default class UsersController extends MvcController {
static users = [{
id: 1,
name: 'user 1',
}]
// GET: /api/users
@GET('')
list() {
return this.ok(UsersController.users);
}
// GET: /api/users/:userId
@GET(':userId')
getById(stringId: string) {
const userId = parseInt(stringId, 10);
const user = UsersController.users.find(user => user.id === userId);
return this.ok(user);
}
// POST: /api/users
@POST('')
add({ name }: { name: string }) {
UsersController.users.push({
id: UsersController.users.length + 1,
name,
});
return this.ok('user is added');
}
}
If you want to get query params and body content, you have to connect another middleware that will handle requests before MvcMiddleware like below.
// src/index.ts
import express, { Router } from 'express';
import bodyParser from 'body-parser';
import { MvcMiddleware } from 'mvc-middleware';
const expressApp = express();
expressApp.use(bodyParser.json());
expressApp.use(bodyParser.urlencoded());
const mvcMiddleware = new MvcMiddleware(expressApp, container);
// ...