@creamapi/cream
Version:
Concise REST API Maker - An extension library for express to create REST APIs faster
233 lines (232 loc) • 8.14 kB
JavaScript
;
/*
* Copyright 2024 Raul Radu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressApplication = void 0;
const ExpressService_1 = require("./ExpressService/ExpressService");
/**
* This class is the main class for your Cream-based REST API
* It will handle controllers, services and will communicate with
* express for you.
*
* @example To use it you can either extend from it or create a new object
* ```ts
* import express from "express";
* import { ExpressApplication } from "@creamapi/cream";
*
* let expressApp = express();
* expressApp.use(express.json());
* let app = new ExpressApplication(expressApp, 4040);
* app.addControllers([<add your controller here>]);
* app.start();
* ```
*/
class ExpressApplication {
app;
_errorHandler;
/**
* The map of active and registered controllers.
* The key will be the name of the controller.
* By this I mean the literal class name.
* Only objects that extend ExpressModule can be
* used as a controller
*/
controllers;
/**
* The map of active and registered services.
* The key will be the id given to the service when describing it.
*/
services;
/**
* The port to which the server will be bounded to.
*/
port;
/**
* The server instance given by the express API
*/
server;
/**
* @param app is the express application that will handle the requests.
* @param port is the port that the server will be bound to
* @param _errorHandler is you custom implementation of the error handler that extends ExpressErrorHandler
*/
constructor(app, port, _errorHandler) {
this.app = app;
this._errorHandler = _errorHandler;
this.controllers = new Map();
this.port = port;
this.services = new Map();
}
/**
* This attribute setter allows for setting a new custom error handler
*/
set errorHandler(v) {
this._errorHandler = v;
}
/**
* This method adds a controller that handles a specific URL Zone
* declared by the ExpressController decorator
* @param controller An instance of the controller that handles the
* specific route
*/
addController(controller) {
if (controller.router.stack.length == 0) {
throw Error('Controller should have at least one express call asssociated with it');
}
if (this.controllers.get(controller.className)) {
throw Error('Controller ' + controller.className + ' is already registered!');
}
let currInstance = this.controllers
.set(controller.className, controller)
.get(controller.className);
let middlewareList = currInstance.middlewareList.map((instance) => instance.handle.bind(instance));
if (middlewareList.length > 0) {
this.app.use(currInstance.baseUrl, middlewareList);
}
this.app.use(currInstance.baseUrl, currInstance.router);
console.log('Registered controller', currInstance.className, 'to', currInstance.baseUrl);
currInstance.app = this;
}
/**
* This method adds a list of controllers to the current application
* It will add them in the provided order
* @param controllers The list of controllers to be added to the current application
* @returns void
*/
addControllers(controllers) {
for (let controller of controllers) {
this.addController(controller);
}
}
/**
* This method adds a service to the current application
* The service must be uniquely identified by @ExpressSerivce.IdentifiedBy(id: string)
* decorator
*
* @param service the service to be added to the current application
* @returns void
*/
addService(service) {
if (this.services.get(service.id)) {
throw Error('Service ' + service.id + ' is already registered!');
}
this.services.set(service.id, service);
service.app = this;
}
/**
* Adds a list of services to the current application
* Remember that the services must be uniquely identified
* @param services The list of services to be added
*/
addServices(services) {
for (let service of services) {
this.addService(service);
}
}
/**
* This method is an interface to express for providing a custom error
* handler
*/
handleErrors(err, req, res, next) {
if (this._errorHandler) {
this._errorHandler.handle(err, req, res);
}
else {
next(err);
}
}
/**
* This method initializes all the registered services.
* It tries to initialize all services before throwing an error
* In this way the developer can see all the issues at once and
* doesn't need to recompile for each error
* @returns true if all services are correctly intialized, false otherwise
*/
async initServices() {
let initStatus = true;
for (let service of this.services) {
console.log('Initializing service', service[0], '...');
let res = false;
try {
res = await service[1].init();
initStatus = res ? initStatus : false;
}
catch (e) {
console.log(e);
res = false;
initStatus = false;
}
if (res) {
console.log('Service', service[0], 'successfully initialized');
}
else {
console.log('Could not initialize service', service[0]);
}
}
return initStatus;
}
/**
* Starts the express application
*/
async start() {
let boundFn = this.handleErrors.bind(this);
this.app.use(boundFn);
let res = await this.initServices();
if (!res) {
throw Error('Failed to initialize all the services, see logs for more info.');
}
this.server = this.app.listen(this.port, () => {
console.log('Listening on', this.port);
});
}
/**
* This function is used to stop the server on purpose
* @returns void
* @throws any generated error by Server.close
*/
async stop() {
return new Promise((resolve, reject) => {
console.log('Stopping server...');
this.server.close(async (err) => {
if (err) {
reject(err);
}
console.log('Server closed successfully');
resolve();
});
});
}
/**
* @returns the active express application
*/
getExpressApp() {
return this.app;
}
/**
* This method is used when we want to retreive a shared service
* within a controller. This is useful for example when we want to share
* user data among the services but we don't want to access the database
* everytime so a runtime service that is synced with the DB but caches data
* locally can be used.
* @param serviceId the service identifier that is given with IdentifiedBy decorator
* @returns the requested service or undefined if the service was not found
*/
getService(service) {
let serviceId = Reflect.getMetadata(ExpressService_1.SERVICE_ID_METADATA, service.prototype);
return this.services.get(serviceId);
}
}
exports.ExpressApplication = ExpressApplication;