arrow-express
Advanced tools
Comparing version 1.0.1 to 1.1.0
@@ -118,15 +118,19 @@ "use strict"; | ||
} | ||
this._express[route.getMethod()]("/".concat(routePath), this.createApplicationRequestHandler(route.getRequestHandler())); | ||
this._express[route.getMethod()]("/".concat(routePath), this.createApplicationRequestHandler(route, controller)); | ||
}; | ||
AppConfigurator.prototype.createApplicationRequestHandler = function (routeRequestHandler) { | ||
AppConfigurator.prototype.createApplicationRequestHandler = function (route, controller) { | ||
var _this = this; | ||
return function (req, res) { return __awaiter(_this, void 0, void 0, function () { | ||
var response, error_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
var context, response, error_1; | ||
var _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
_a.trys.push([0, 2, 3, 4]); | ||
return [4 /*yield*/, routeRequestHandler(req, res)]; | ||
_b.trys.push([0, 3, 4, 5]); | ||
return [4 /*yield*/, ((_a = controller.getHandler()) === null || _a === void 0 ? void 0 : _a(req, res))]; | ||
case 1: | ||
response = _a.sent(); | ||
context = _b.sent(); | ||
return [4 /*yield*/, route.getRequestHandler()(req, res, context)]; | ||
case 2: | ||
response = _b.sent(); | ||
if (AppConfigurator.canSendResponse(res)) { | ||
@@ -138,5 +142,5 @@ if (!res.statusCode) { | ||
} | ||
return [3 /*break*/, 4]; | ||
case 2: | ||
error_1 = _a.sent(); | ||
return [3 /*break*/, 5]; | ||
case 3: | ||
error_1 = _b.sent(); | ||
if (AppConfigurator.canSendResponse(res)) { | ||
@@ -150,7 +154,7 @@ if (error_1 instanceof request_error_1.RequestError) { | ||
} | ||
return [3 /*break*/, 4]; | ||
case 3: | ||
return [3 /*break*/, 5]; | ||
case 4: | ||
this.logRequest(req, res); | ||
return [7 /*endfinally*/]; | ||
case 4: return [2 /*return*/]; | ||
case 5: return [2 /*return*/]; | ||
} | ||
@@ -157,0 +161,0 @@ }); |
@@ -1,6 +0,8 @@ | ||
import { RouteConfigurator } from '../route/route'; | ||
export declare class ControllerConfiguration { | ||
import { RouteConfigurator } from "../route/route"; | ||
export type ControllerHandler<C> = (request: any, response: any) => Promise<C>; | ||
export declare class ControllerConfiguration<C = unknown> { | ||
private _prefix; | ||
private _controllers; | ||
private _routes; | ||
private _handler; | ||
/** | ||
@@ -10,3 +12,3 @@ * Register child controller in controller | ||
*/ | ||
registerController(controller: ControllerConfiguration): ControllerConfiguration; | ||
registerController(controller: ControllerConfiguration): this; | ||
/** | ||
@@ -16,3 +18,3 @@ * Register array of controllers in controller | ||
*/ | ||
registerControllers(...controllers: ControllerConfiguration[]): ControllerConfiguration; | ||
registerControllers(...controllers: ControllerConfiguration[]): this; | ||
/** | ||
@@ -22,3 +24,3 @@ * Register route in controller | ||
*/ | ||
registerRoute(route: RouteConfigurator): ControllerConfiguration; | ||
registerRoute(route: RouteConfigurator<C>): this; | ||
/** | ||
@@ -28,3 +30,3 @@ * Register array of routes in controller | ||
*/ | ||
registerRoutes(...routes: RouteConfigurator[]): ControllerConfiguration; | ||
registerRoutes(...routes: RouteConfigurator<C>[]): this; | ||
/** | ||
@@ -34,7 +36,13 @@ * Register controller prefix which will be used by all routes | ||
*/ | ||
prefix(prefix: string): ControllerConfiguration; | ||
prefix(prefix: string): this; | ||
/** | ||
* Register controller handler which will be used by all routes | ||
* @param handler - ControllerHandler function | ||
*/ | ||
handler<NewContext>(handler: ControllerHandler<NewContext>): ControllerConfiguration<NewContext>; | ||
getPrefix(): string; | ||
getRoutes(): RouteConfigurator[]; | ||
getRoutes(): RouteConfigurator<C>[]; | ||
getControllers(): ControllerConfiguration[]; | ||
getHandler(): ControllerHandler<C> | undefined; | ||
} | ||
export declare function Controller(): ControllerConfiguration; | ||
export declare function Controller<C = unknown>(): ControllerConfiguration<C>; |
@@ -6,3 +6,3 @@ "use strict"; | ||
function ControllerConfiguration() { | ||
this._prefix = ''; | ||
this._prefix = ""; | ||
this._controllers = []; | ||
@@ -59,2 +59,10 @@ this._routes = []; | ||
}; | ||
/** | ||
* Register controller handler which will be used by all routes | ||
* @param handler - ControllerHandler function | ||
*/ | ||
ControllerConfiguration.prototype.handler = function (handler) { | ||
this._handler = handler; | ||
return this; | ||
}; | ||
ControllerConfiguration.prototype.getPrefix = function () { | ||
@@ -69,2 +77,5 @@ return this._prefix; | ||
}; | ||
ControllerConfiguration.prototype.getHandler = function () { | ||
return this._handler; | ||
}; | ||
return ControllerConfiguration; | ||
@@ -71,0 +82,0 @@ }()); |
export { Application, AppConfigurator } from "./application/application"; | ||
export { Controller, ControllerConfiguration } from "./controller/controller"; | ||
export { Route, RouteConfigurator } from "./route/route"; | ||
export { Controller, ControllerConfiguration, ControllerHandler } from "./controller/controller"; | ||
export { Route, RouteConfigurator, RouteHandler } from "./route/route"; | ||
export { RequestError } from "./error/request.error"; |
import Express from "express"; | ||
export type RouteHandler = (request: Express.Request, response: Express.Response) => unknown; | ||
export type RequestHandler = (request: Express.Request, response: Express.Response) => unknown; | ||
export type RouteHandler<C = unknown> = (request: Express.Request, response: Express.Response, context?: C) => unknown; | ||
export type HttpMethod = "get" | "post" | "head" | "put" | "delete" | "options" | "patch"; | ||
export declare class RouteConfigurator { | ||
export declare class RouteConfigurator<C = unknown> { | ||
private _method; | ||
@@ -13,3 +12,3 @@ private _path; | ||
*/ | ||
method(method: HttpMethod): RouteConfigurator; | ||
method(method: HttpMethod): this; | ||
/** | ||
@@ -19,3 +18,3 @@ * Register path of route alongside with prefix it is used to create full path | ||
*/ | ||
path(path: string): RouteConfigurator; | ||
path(path: string): this; | ||
/** | ||
@@ -25,3 +24,3 @@ * Set request handler, here you can handle request | ||
*/ | ||
handler(handler: RouteHandler): RouteConfigurator; | ||
handler(handler: RouteHandler<C>): this; | ||
getMethod(): string; | ||
@@ -33,4 +32,4 @@ getPath(): string; | ||
*/ | ||
getRequestHandler(): RequestHandler; | ||
getRequestHandler(): RouteHandler<C>; | ||
} | ||
export declare function Route(): RouteConfigurator; | ||
export declare function Route<C>(): RouteConfigurator<C>; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -78,8 +42,3 @@ exports.Route = exports.RouteConfigurator = void 0; | ||
RouteConfigurator.prototype.getRequestHandler = function () { | ||
var _this = this; | ||
return function (request, response) { return __awaiter(_this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
return [2 /*return*/, this._handler(request, response)]; | ||
}); | ||
}); }; | ||
return this._handler; | ||
}; | ||
@@ -86,0 +45,0 @@ return RouteConfigurator; |
@@ -5,12 +5,11 @@ import { Route, RouteConfigurator } from "arrow-express"; | ||
import { UserService } from "../../../data/services/user.service"; | ||
import { AuthorizeGuard } from "../../guards/authorize.guard"; | ||
import { UserContext } from "../../guards/authorize.guard"; | ||
export function GetMyselfRoute(userService: UserService): RouteConfigurator { | ||
return Route() | ||
export function GetMyselfRoute(userService: UserService): RouteConfigurator<UserContext> { | ||
return Route<UserContext>() | ||
.method("get") | ||
.path("myself") | ||
.handler(async (req): Promise<User> => { | ||
const context = await AuthorizeGuard(req); | ||
.handler(async (req, res, context): Promise<User> => { | ||
return await userService.getUserById(context.userId); | ||
}); | ||
} |
@@ -5,12 +5,11 @@ import { Route, RouteConfigurator } from "arrow-express"; | ||
import { UserService } from "../../../data/services/user.service"; | ||
import { AuthorizeGuard } from "../../guards/authorize.guard"; | ||
import { UserContext } from "../../guards/authorize.guard"; | ||
export function GetUserByIdRoute(userService: UserService): RouteConfigurator { | ||
return Route() | ||
export function GetUserByIdRoute(userService: UserService): RouteConfigurator<UserContext> { | ||
return Route<UserContext>() | ||
.method("get") | ||
.path(":id") | ||
.handler(async (req): Promise<User> => { | ||
await AuthorizeGuard(req); | ||
return await userService.getUserById(Number(req.params.id)); | ||
}); | ||
} |
@@ -1,2 +0,2 @@ | ||
import { Controller, ControllerConfiguration, RouteConfigurator } from "arrow-express"; | ||
import { Controller, ControllerConfiguration } from "arrow-express"; | ||
@@ -6,5 +6,9 @@ import { UserService } from "../../data/services/user.service"; | ||
import { GetMyselfRoute } from "./routes/getMyself.route"; | ||
import { AuthorizeGuard } from "../guards/authorize.guard"; | ||
export function UserController(userService: UserService): ControllerConfiguration { | ||
return Controller().prefix("users").registerRoutes(GetUserByIdRoute(userService), GetMyselfRoute(userService)); | ||
return Controller() | ||
.handler(AuthorizeGuard) | ||
.prefix("users") | ||
.registerRoutes(GetUserByIdRoute(userService), GetMyselfRoute(userService)); | ||
} |
// Express packages | ||
import Express from 'express'; | ||
import Compression from 'compression'; | ||
import cors from 'cors'; | ||
import Express from "express"; | ||
import Compression from "compression"; | ||
import cors from "cors"; | ||
// Api packages | ||
import {Application} from "arrow-express"; | ||
import {UserController} from "./api/user/user.controller"; | ||
import { Application } from "arrow-express"; | ||
import { UserController } from "./api/user/user.controller"; | ||
// Data packages | ||
import { UserService } from './data/services/user.service'; | ||
import { UserService } from "./data/services/user.service"; | ||
@@ -22,3 +22,3 @@ async function startServer() { | ||
Application({ | ||
app: expressApplication | ||
app: expressApplication, | ||
}) | ||
@@ -25,0 +25,0 @@ .registerController(UserController(userService)) |
@@ -1,9 +0,9 @@ | ||
import Express from 'express'; | ||
import Express from "express"; | ||
import { Application } from './application'; | ||
import { Controller } from '../controller/controller'; | ||
import { Route } from '../route/route'; | ||
import { RequestError } from '../error/request.error'; | ||
import {mocked} from "ts-jest/utils"; | ||
import {ConfigurationError} from "../error/configuration.error"; | ||
import { Application } from "./application"; | ||
import { Controller } from "../controller/controller"; | ||
import { Route } from "../route/route"; | ||
import { RequestError } from "../error/request.error"; | ||
import { mocked } from "ts-jest/utils"; | ||
import { ConfigurationError } from "../error/configuration.error"; | ||
@@ -15,8 +15,7 @@ const ExpressAppStub: Express.Application = { | ||
_router: { | ||
stack: [] | ||
} | ||
stack: [], | ||
}, | ||
} as unknown as Express.Application; | ||
describe('Application', () => { | ||
describe("Application", () => { | ||
afterEach(() => { | ||
@@ -27,103 +26,66 @@ mocked(ExpressAppStub.use).mockReset(); | ||
}); | ||
describe('configure', () => { | ||
it('should throw error if app is configured multiple times', () => { | ||
const testApplication = Application({app: ExpressAppStub, logRequests: false}); | ||
describe("configure", () => { | ||
it("should throw error if app is configured multiple times", () => { | ||
const testApplication = Application({ app: ExpressAppStub, logRequests: false }); | ||
testApplication.configure(false); | ||
expect(testApplication.configure). toThrow(); | ||
expect(testApplication.configure).toThrow(); | ||
}); | ||
describe('route registration', () => { | ||
it('should register post route', () => { | ||
describe("route registration", () => { | ||
it("should register post route", () => { | ||
const handlerSpy = jest.fn(); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController( | ||
Controller() | ||
.prefix('prefix') | ||
.registerRoute( | ||
Route() | ||
.method('post') | ||
.path('path') | ||
.handler(handlerSpy) | ||
) | ||
).configure(false); | ||
expect(ExpressAppStub.post).toHaveBeenCalledWith('/prefix/path', expect.any(Function)); | ||
Controller().prefix("prefix").registerRoute(Route().method("post").path("path").handler(handlerSpy)) | ||
) | ||
.configure(false); | ||
expect(ExpressAppStub.post).toHaveBeenCalledWith("/prefix/path", expect.any(Function)); | ||
}); | ||
it('should register get route', () => { | ||
it("should register get route", () => { | ||
const handlerSpy = jest.fn(); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController( | ||
Controller().prefix('prefix') | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.path('') | ||
.handler(handlerSpy) | ||
) | ||
).configure(false); | ||
expect(ExpressAppStub.get).toHaveBeenCalledWith('/prefix', expect.any(Function)); | ||
Controller().prefix("prefix").registerRoute(Route().method("get").path("").handler(handlerSpy)) | ||
) | ||
.configure(false); | ||
expect(ExpressAppStub.get).toHaveBeenCalledWith("/prefix", expect.any(Function)); | ||
}); | ||
it('should register route without path', () => { | ||
it("should register route without path", () => { | ||
const handlerSpy = jest.fn(); | ||
Application({app: ExpressAppStub}) | ||
.registerController( | ||
Controller().prefix('prefix') | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(handlerSpy) | ||
) | ||
).configure(false); | ||
expect(ExpressAppStub.get).toHaveBeenCalledWith('/prefix', expect.any(Function)); | ||
Application({ app: ExpressAppStub }) | ||
.registerController(Controller().prefix("prefix").registerRoute(Route().method("get").handler(handlerSpy))) | ||
.configure(false); | ||
expect(ExpressAppStub.get).toHaveBeenCalledWith("/prefix", expect.any(Function)); | ||
}); | ||
it('should register route without path and prefix', () => { | ||
it("should register route without path and prefix", () => { | ||
const handlerSpy = jest.fn(); | ||
Application({app: ExpressAppStub}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(handlerSpy) | ||
) | ||
).configure(false); | ||
expect(ExpressAppStub.get).toHaveBeenCalledWith('/', expect.any(Function)); | ||
Application({ app: ExpressAppStub }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(handlerSpy))) | ||
.configure(false); | ||
expect(ExpressAppStub.get).toHaveBeenCalledWith("/", expect.any(Function)); | ||
}); | ||
it('should throw configuration error when route without method is registered', () => { | ||
it("should throw configuration error when route without method is registered", () => { | ||
const handlerSpy = jest.fn(); | ||
const app = Application({app: ExpressAppStub}) | ||
.registerController( | ||
Controller().prefix('prefix') | ||
.registerRoute( | ||
Route() | ||
.path('') | ||
.handler(handlerSpy) | ||
) | ||
); | ||
const app = Application({ app: ExpressAppStub }).registerController( | ||
Controller().prefix("prefix").registerRoute(Route().path("").handler(handlerSpy)) | ||
); | ||
expect(() => app.configure(false)).toThrow(ConfigurationError); | ||
}); | ||
describe('sub controllers', () => { | ||
it('should register sub controller route', () => { | ||
describe("sub controllers", () => { | ||
it("should register sub controller route", () => { | ||
const handlerSpy = jest.fn(); | ||
Application({app: ExpressAppStub}) | ||
Application({ app: ExpressAppStub }) | ||
.registerController( | ||
Controller() | ||
.prefix('root') | ||
.registerController( | ||
Controller() | ||
.prefix('sub') | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(handlerSpy) | ||
) | ||
) | ||
).configure(false); | ||
expect(ExpressAppStub.get).toHaveBeenCalledWith('/root/sub', expect.any(Function)); | ||
.prefix("root") | ||
.registerController(Controller().prefix("sub").registerRoute(Route().method("get").handler(handlerSpy))) | ||
) | ||
.configure(false); | ||
expect(ExpressAppStub.get).toHaveBeenCalledWith("/root/sub", expect.any(Function)); | ||
}); | ||
@@ -133,6 +95,6 @@ }); | ||
}); | ||
describe('request handling', () => { | ||
describe("request handling", () => { | ||
let resSpy: Express.Response; | ||
beforeEach(() => { | ||
resSpy= { | ||
resSpy = { | ||
status: jest.fn().mockImplementation(() => resSpy), | ||
@@ -143,44 +105,26 @@ send: jest.fn().mockImplementation(() => resSpy), | ||
}); | ||
it('should response 200', async () => { | ||
it("should response 200", async () => { | ||
const spy = jest.fn(); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(spy) | ||
) | ||
).configure(false); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(spy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(resSpy.status).toHaveBeenCalledWith(200); | ||
}); | ||
it('should not override statusCode', async () => { | ||
it("should not override statusCode", async () => { | ||
const spy = jest.fn(); | ||
resSpy.statusCode = 301; | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(spy) | ||
) | ||
).configure(false); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(spy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(resSpy.status).not.toHaveBeenCalledWith(200); | ||
}); | ||
it('should not response 200 when res is not writable', async () => { | ||
it("should not response 200 when res is not writable", async () => { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
(resSpy.writableEnded as boolean) = true; | ||
const spy = jest.fn(); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(spy) | ||
) | ||
).configure(false); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(spy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
@@ -190,60 +134,36 @@ expect(resSpy.status).not.toHaveBeenCalled(); | ||
describe('error handling', () => { | ||
it('should response code 500 by default', async () => { | ||
describe("error handling", () => { | ||
it("should response code 500 by default", async () => { | ||
const spy = jest.fn().mockRejectedValue(new RequestError()); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(spy) | ||
) | ||
).configure(false); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(spy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(resSpy.status).toHaveBeenCalledWith(500); | ||
}); | ||
it('should response code 500 on non RequestError', async () => { | ||
it("should response code 500 on non RequestError", async () => { | ||
const spy = jest.fn().mockRejectedValue(new Error()); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(spy) | ||
) | ||
).configure(false); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(spy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(resSpy.status).toHaveBeenCalledWith(500); | ||
}); | ||
it('should response 404', async () => { | ||
it("should response 404", async () => { | ||
const spy = jest.fn().mockRejectedValue(new RequestError(404)); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(spy) | ||
) | ||
).configure(false); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(spy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(resSpy.status).toHaveBeenCalledWith(404); | ||
}); | ||
it('should send error response', async () => { | ||
it("should send error response", async () => { | ||
const response = { | ||
code: 1, | ||
message: 'msg' | ||
message: "msg", | ||
}; | ||
const spy = jest.fn().mockRejectedValue(new RequestError(401, response)); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(spy) | ||
) | ||
).configure(false); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(spy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
@@ -253,20 +173,44 @@ expect(resSpy.send).toHaveBeenCalledWith(response); | ||
}); | ||
it('should not response', async () => { | ||
it("should not response", async () => { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
(resSpy.writableEnded as boolean) = true; | ||
const spy = jest.fn().mockRejectedValue(new Error()); | ||
Application({app: ExpressAppStub, logRequests: false}) | ||
.registerController( | ||
Controller() | ||
.registerRoute( | ||
Route() | ||
.method('get') | ||
.handler(spy) | ||
) | ||
).configure(false); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().registerRoute(Route().method("get").handler(spy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(resSpy.status).not.toHaveBeenCalled(); | ||
}); | ||
it("should pass context from controller handler to route handler", async () => { | ||
const spy = jest.fn().mockResolvedValue("context"); | ||
const routeSpy = jest.fn(); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().handler(spy).registerRoute(Route().method("get").handler(routeSpy))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(routeSpy).toHaveBeenCalledWith(expect.anything(), expect.anything(), "context"); | ||
}); | ||
it("should call controller handler", async () => { | ||
const spy = jest.fn().mockRejectedValue(new Error()); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().handler(spy).registerRoute(Route().method("get"))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(spy).toHaveBeenCalled(); | ||
}); | ||
it("should send error response from controller handler", async () => { | ||
const response = { | ||
code: 1, | ||
message: "msg", | ||
}; | ||
const spy = jest.fn().mockRejectedValue(new RequestError(401, response)); | ||
Application({ app: ExpressAppStub, logRequests: false }) | ||
.registerController(Controller().handler(spy).registerRoute(Route().method("get"))) | ||
.configure(false); | ||
await mocked(ExpressAppStub.get).mock.calls[0][1]({} as never, resSpy); | ||
expect(resSpy.send).toHaveBeenCalledWith(response); | ||
expect(resSpy.status).toHaveBeenCalledWith(401); | ||
}); | ||
}); | ||
}); | ||
}); |
import Express from "express"; | ||
import { ControllerConfiguration } from "../controller/controller"; | ||
import { RequestHandler, RouteConfigurator } from "../route/route"; | ||
import { RouteHandler, RouteConfigurator } from "../route/route"; | ||
import { RequestError } from "../error/request.error"; | ||
@@ -87,9 +87,13 @@ import { ConfigurationError } from "../error/configuration.error"; | ||
this._express[route.getMethod()](`/${routePath}`, this.createApplicationRequestHandler(route.getRequestHandler())); | ||
this._express[route.getMethod()](`/${routePath}`, this.createApplicationRequestHandler(route, controller)); | ||
} | ||
private createApplicationRequestHandler(routeRequestHandler: RequestHandler): Express.RequestHandler { | ||
private createApplicationRequestHandler( | ||
route: RouteConfigurator, | ||
controller: ControllerConfiguration | ||
): Express.RequestHandler { | ||
return async (req: Express.Request, res: Express.Response) => { | ||
try { | ||
const response = await routeRequestHandler(req, res); | ||
const context = await controller.getHandler()?.(req, res); | ||
const response = await route.getRequestHandler()(req, res, context); | ||
if (AppConfigurator.canSendResponse(res)) { | ||
@@ -96,0 +100,0 @@ if (!res.statusCode) { |
@@ -1,7 +0,9 @@ | ||
import {RouteConfigurator} from '../route/route'; | ||
import { RouteConfigurator } from "../route/route"; | ||
export class ControllerConfiguration { | ||
private _prefix = '' | ||
export type ControllerHandler<C> = (request: any, response: any) => Promise<C>; | ||
export class ControllerConfiguration<C = unknown> { | ||
private _prefix = ""; | ||
private _controllers: ControllerConfiguration[] = []; | ||
private _routes: RouteConfigurator[] = []; | ||
private _routes: RouteConfigurator<C>[] = []; | ||
private _handler: ControllerHandler<C> | undefined; | ||
@@ -12,3 +14,3 @@ /** | ||
*/ | ||
registerController(controller: ControllerConfiguration): ControllerConfiguration { | ||
registerController(controller: ControllerConfiguration): this { | ||
this._controllers.push(controller); | ||
@@ -18,3 +20,2 @@ return this; | ||
/** | ||
@@ -24,3 +25,3 @@ * Register array of controllers in controller | ||
*/ | ||
registerControllers(...controllers: ControllerConfiguration[]): ControllerConfiguration { | ||
registerControllers(...controllers: ControllerConfiguration[]): this { | ||
controllers.forEach(this.registerController.bind(this)); | ||
@@ -34,3 +35,3 @@ return this; | ||
*/ | ||
registerRoute(route: RouteConfigurator): ControllerConfiguration { | ||
registerRoute(route: RouteConfigurator<C>): this { | ||
this._routes.push(route); | ||
@@ -44,3 +45,3 @@ return this; | ||
*/ | ||
registerRoutes(...routes: RouteConfigurator[]): ControllerConfiguration { | ||
registerRoutes(...routes: RouteConfigurator<C>[]): this { | ||
routes.forEach(this.registerRoute.bind(this)); | ||
@@ -53,3 +54,3 @@ return this; | ||
*/ | ||
prefix(prefix: string): ControllerConfiguration { | ||
prefix(prefix: string): this { | ||
this._prefix = prefix; | ||
@@ -59,2 +60,11 @@ return this; | ||
/** | ||
* Register controller handler which will be used by all routes | ||
* @param handler - ControllerHandler function | ||
*/ | ||
handler<NewContext>(handler: ControllerHandler<NewContext>): ControllerConfiguration<NewContext> { | ||
this._handler = handler as unknown as ControllerHandler<C>; | ||
return this as unknown as ControllerConfiguration<NewContext>; | ||
} | ||
getPrefix(): string { | ||
@@ -64,3 +74,3 @@ return this._prefix; | ||
getRoutes(): RouteConfigurator[] { | ||
getRoutes(): RouteConfigurator<C>[] { | ||
return this._routes; | ||
@@ -72,6 +82,10 @@ } | ||
} | ||
getHandler(): ControllerHandler<C> | undefined { | ||
return this._handler; | ||
} | ||
} | ||
export function Controller(): ControllerConfiguration { | ||
return new ControllerConfiguration(); | ||
export function Controller<C = unknown>(): ControllerConfiguration<C> { | ||
return new ControllerConfiguration<C>(); | ||
} |
/* istanbul ignore file */ | ||
export { Application, AppConfigurator } from "./application/application"; | ||
export { Controller, ControllerConfiguration } from "./controller/controller"; | ||
export { Route, RouteConfigurator } from "./route/route"; | ||
export { RequestError } from "./error/request.error"; | ||
export { Controller, ControllerConfiguration, ControllerHandler } from "./controller/controller"; | ||
export { Route, RouteConfigurator, RouteHandler } from "./route/route"; | ||
export { RequestError } from "./error/request.error"; |
import Express from "express"; | ||
export type RouteHandler = (request: Express.Request, response: Express.Response) => unknown; | ||
export type RequestHandler = (request: Express.Request, response: Express.Response) => unknown; | ||
export type RouteHandler<C = unknown> = (request: Express.Request, response: Express.Response, context?: C) => unknown; | ||
export type HttpMethod = "get" | "post" | "head" | "put" | "delete" | "options" | "patch"; | ||
export class RouteConfigurator { | ||
export class RouteConfigurator<C = unknown> { | ||
private _method: HttpMethod; | ||
private _path: string; | ||
private _handler: RouteHandler; | ||
private _handler: RouteHandler<C>; | ||
@@ -16,3 +15,3 @@ /** | ||
*/ | ||
method(method: HttpMethod): RouteConfigurator { | ||
method(method: HttpMethod): this { | ||
this._method = method || "get"; | ||
@@ -26,3 +25,3 @@ return this; | ||
path(path: string): RouteConfigurator { | ||
path(path: string): this { | ||
this._path = path; | ||
@@ -36,3 +35,3 @@ return this; | ||
*/ | ||
handler(handler: RouteHandler): RouteConfigurator { | ||
handler(handler: RouteHandler<C>): this { | ||
this._handler = handler; | ||
@@ -54,11 +53,9 @@ return this; | ||
*/ | ||
getRequestHandler(): RequestHandler { | ||
return async (request: Express.Request, response: Express.Response) => { | ||
return this._handler(request, response); | ||
}; | ||
getRequestHandler(): RouteHandler<C> { | ||
return this._handler; | ||
} | ||
} | ||
export function Route(): RouteConfigurator { | ||
return new RouteConfigurator(); | ||
export function Route<C>(): RouteConfigurator<C> { | ||
return new RouteConfigurator<C>(); | ||
} |
{ | ||
"name": "arrow-express", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "Library to bootstrap express applications with zero configuration", | ||
@@ -25,2 +25,3 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@changesets/cli": "^2.26.1", | ||
"@types/express": "^4.17.11", | ||
@@ -27,0 +28,0 @@ "@types/jest": "^26.0.20", |
44
792000
14
15731