npm init -y
npm install tsnode-express --save
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"declaration": true,
"strict": false,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
}
}
Create index.ts file
import http from 'http';
import { Application } from 'tsnode-express';
const application = new Application();
application.start((express, config) => {
http.createServer(express).listen(3000, () => {
console.log('Server listening')
})
}) ./node_modules/.bin/ts-node index.ts
Expected result for GET http://localhost:3000/health
{
"status": "live"
}import { Controller, Get } from "tsnode-express";
@Controller('some')
class SomeController {
@Get('/')
getSuccess(args: IRequestArguments) {
return {
data: "success"
}
}
}Expected result fot the GET http://localhost:3000/some
{
"data": "success"
}The application support dependency injection mechanism
import { Service } from "tsnode-express";
@Service()
class SomeService {
getSomeData() {
return {
data: "from service"
}
}
}import { Controller, Get } from "tsnode-express";
@Controller('some')
class SomeController {
constructor(public someService: SomeService)
@Get('/service')
getFromService(args: IRequestArguments) {
return this.someService.getSomeData()
}
}Expected result fot the GET http://localhost:3000/some/service
{
"data": "from service"
}Insert before applocation.start() function
application.useConfig((config) => {
config.test = 'test config field';
});import { Service, ConfigProvider } from "tsnode-express";
@Service()
class SomeService {
constructor(public configProvider: ConfigProvider) {}
getTestConfig() {
return {
data: "from service",
configField: this.configProvider.test
}
}
}Expected result fot the GET http://localhost:3000/some/service
{
"data": "from service",
"configField": "test config field"
}import { Controller, Post } from "tsnode-express";
@Controller('some')
class SomeController {
@Post('echo/:param')
echo({ body, params, query }: IRequestArguments) {
return {
body: body.data,
params: params.param,
query: query.echo
}
}
}Expected result fot the POST http://localhost:3000/some/echo/echoParam?echo=echoQuery
{
"data": "echoBody"
}{
"body": "echoBody",
"params": "echoParam",
"query": "echoQuery"
}In afrer hook available result object wich collect data from original method
@Before('GET', '/hooks')
beforeWithHooks(req: IRequest, res: IResponse, next: Function) {
req.body.before = 'before hook'
}
@Get('/hooks')
withHooks(args: IRequestArguments) {
return {
origin: 'original method'
}
}
@After('GET', '/hooks')
afterWithHooks(req: IRequest, res: IResponse, next: Function) {
res.result = Object.assign(req.body, res.result, { after: 'after hook' })
}Expected result fot the GET http://localhost:3000/some/hooks
{
"before": "before hook",
"origin": "original method",
"after": "after hook"
}Hooks are accepts in first argument method type wich you want define for hook GET, POST etc As second argument you should pass a path to route Hooks can works separately from original function if you don`t need it As example you can work with webhooks and use only @Before wook to validate headers or do with request something else
@Before('GET', '/single-before-hook/:param')
singleBeforeHook(req: IRequest, res: IResponse, next: Function) {
res.send({ break: `Before hook catch ${req.params.param} param` })
}Expected result fot the GET http://localhost:3000:3000/some/single-before-hook/someParam
{
"break": "Before hook catch someParam param"
}@After('GET', '/single-after-hook/:param')
singleAfterHook(req: IRequest, res: IResponse, next: Function) {
res.send({ break: `After hook catch ${req.params.param} param` })
}Expected result fot the GET http://localhost:3000:3000/some/single-after-hook/someParam
{
"break": "After hook catch someParam param"
}Verify function should terurns the promise
import { IAuthProvider, IAuthTarget } from "tsnode-express";
@Reflect.metadata('design', 'paramtypes')
class AuthProvider implements IAuthProvider {
async verify(token: string, authTarget: IAuthTarget): Promise<any> {
// veryfy token here and return the obj witch you want to see in req otions
return { name: 'John Doe' };
}
} AuthTarget {
controller: 'AuthController',
method: 'get',
basePath: '/auth',
path: '/me',
functionName: 'me',
role: 'default',
roles: [ 'default', 'admin', 'super' ],
fullPath: '/auth/me' }
Insert before applocation.start() function and define where and how you want to handle auth token
application.useAuthorizationProvider(AuthProvider, (options: IAuthOptions) => {
// Define token handler field or leave empty
})By defalult options looks like
AuthOptions {
authorizationHeader: 'authorization',
authorizationQueryParam: 'access_token',
authorizationBodyField: 'accessToken' }
@Authorization()
@Controller('auth')
export class AuthController {
}Rigth now any request to /auth will be sequre by AuthProvider verify handler
@Authorization({ role: 'default', roles: ['admin', 'user'] })@Authorization decorator accept role/roles otions which will be inclured to AuthTarget object
in AuthProvider verify handler
@Get('me', { role: 'default' })Route decorator also accept role/roles otions which will be inclured to AuthTarget object
in AuthProvider verify handler
@Get('sign-in', { auth: false })Also you can exclude some routes from authorization inside sequre controller This migth be helps when controller needs to be sequre but some routes should be public For example in case when you configure webhook with custom auth
logLevels might contains array as described below
- info - logging the incoming request(path, target functions)
- success - runs once when server starts and display sucessfuly builded routes
- error - always print stactrace and display errors without statusCodes
- warning - display errors which was throwed mannualy and contains the statusCodes
printStack says to application is the print stack trace required on warnings.
These options by defaulf is empty
application.useConfig((config) => {
config.logLevels = ['info', 'success', 'error', 'warning'];
config.printStack = false;
});Constructor of applications retunrs an express instance so you can configure it before application builds
Example how to configure CORS
const application = new Application((express) => {
express.use((req: IRequest, res: IResponse, next: Function) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
next();
});
});const application = new Application();
application
.use(cors())
.use(/** another handler */)
.use(/** another handler */)
});The applications uses as error lib https://www.npmjs.com/package/ts-http-errors
So applications allows to override handleError and handleNotFound methods
which uses as express middleware
const application = new Application();
app.handleError = function (err: ExtendedError, req: IRequest, res: IResponse, next: Function) {
//put your code here
}You can choose another way and extend your own class from application
class OwnApp extends Application {
constructor() {
super()
this.express.use((req: IRequest, res: IResponse, next: Function) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
next();
});
}
}
const app = new OwnApp();
app.start();As the typescript import is different with nodejs require
Application have a simple stub to keep code structural
Example:
- moduleA
- service.ts
- controller.ts
- index.ts
- moduleB
- controller.ts
- service.ts
- index.ts
- index.ts
import * as moduleA from './moduleA';
import * as moduleB from './moduleB';
const application = new Application();
application
.registerModule(moduleA);
.registerModule(moduleB);Application support external injections throuth
public inject<T>(name: string, cb: Function): Application;
public inject<T>(instance: T): Application;So you can inject to application already creates instances or use factory to create incjection. Factory can be async
Exapmple
abstract class IInjectedService {
stub: string
};
class InjectedService extends IInjectedService {
stub: string
constructor(opts) {
super();
Object.assign(this, opts);
}
}
const injectedService: InjectedService = new InjectedService({
stub: 'injected as class'
});
const application = new Application();
application
.inject<InjectedService>(injectedService)
.inject<IInjectedService>('IInjectedService', async () => ({ stub: 'injected as interface' }))And after application start those can be available on services or controllers
@Service()
class SomeService {
constructor(
public injectedService: InjectedService,
public iInjectedService: IInjectedService
) {}