TypeNexus is a great tool for API encapsulation and management. It offers a clean and lightweight way to bundle TypeORM + Express.js functionality, helping you to build applications faster while reducing template code redundancy and type conversion work.
$ npm install typenexus
Its important to set these options in tsconfig.json
file of your project:
{
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
Its important to set these options in package.json
file of your project:
{
"type": "module",
}
import { TypeNexus } from 'typenexus';
(async () => {
const app = new TypeNexus();
await app.start();
// Open in browser http://localhost:3000
})();
./src/controller/UserController.ts
import { TypeNexus, Controller, Param, Body, DataSource } from 'typenexus';
import { Get, Post, Put, Delete, Patch, Delete, Head } from 'typenexus';
import { User } from '../entities/user.entity';
@Controller('/api/users')
export class UserController {
@Get() // => GET /api/users
public async getAll(@DSource() dataSource: DataSource): Promise<User[]> {
return dataSource.manager.find(User);
}
@Get('/:id') // => GET /api/users/:id
public async getById(@Param('id') id: string, @DSource() dataSource: DataSource): Promise<User> {
return dataSource.manager.findOne(User, id);
}
@Post('/:id') // => POST /api/users/:id
public async modify(@Body() body: { name: string; }): Promise<{ name: string; }> {
return { name: body.name + '~~' }
}
@Put('/:id') // => PUT /api/users/:id
public async modify(@Param('id') id: string): Promise<{ uid: string; }> {
return { uid: id }
}
@Delete('/:id') // => DELETE /api/users/:id
public async modify(@Param('id') id: string): Promise<{ uid: string; }> {
return { uid: id }
}
@Patch('/:id') // => PATCH /api/users/:id
public async patch(): Promise<any> {
return { id: 12 }
}
@Head('/:id') // => HEAD /api/users/:id
public async head(): Promise<{ id: number; }> {
return { id: 12 }
}
}
This class will register routes specified in method decorators in your server framework Express.js.
Entity is a class that maps to a database table (or collection when using Postgres
). You can create an entity by defining a new class and mark it with @Entity()
:
./src/entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn, CreateDateColumn } from 'typenexus';
// OR:
import { Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn, CreateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
username: string;
@Column({ select: false })
password: string;
@CreateDateColumn()
createAt: Date;
}
./src/entities/user.entity.ts
import { TypeNexus } from 'typenexus';
import { UserController } from './controller/User.js';
;(async () => {
const app = new TypeNexus();
// ❶ Performs connection to the database.
await app.connect({
type: 'postgres',
host: process.env.HOST || 'localhost',
port: 5432,
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASS || 'wcjiang',
database: process.env.DB_NAME || 'typenexus-base',
synchronize: true,
logging: true,
entities: ['dist/entity/*.js'],
// OR:
// entities: [User],
});
// ❷ 🚨 Please be sure to use it after `app.connect()`.
app.controllers([UserController]);
// ❸ Listen for connections.
await app.start();
})();
Open in browser http://localhost:3000/users. You will see This action returns all users in your browser. If you open http://localhost:3000/api/users/1
you will see This action returns user data.
└── src
├── controller
│ └── User.ts
├── entity
│ └── User.ts
└── index.ts
DataSource
Your interaction with the database is only possible once you setup a DataSource. TypeORM's DataSource holds your database connection settings and establishes initial database connection or connection pool depending on the RDBMS you use.
import { TypeNexus } from 'typenexus';
import crypto from 'crypto';
import User from './entity/User.js'
const app = new TypeNexus(3000, { .... });
await app.connect();
// You can use the DataSource example here.
// 🚨 Please be sure to use it after `app.connect()`.
const repos = app.dataSource.getRepository(User);
// Check if there is an admin account.
const adminUser = await repos.findOneBy({ username: 'wcj' });
if (!adminUser) {
const hashPassword = crypto.createHmac('sha256', '1234').digest('hex');
// Create an admin account.
const user = await repos.create({
username: 'wcj',
name: '管理员',
password: hashPassword,
});
await repos.save(user);
}
// 🚨 Please be sure to use it after `app.connect()`.
app.controllers([UserController]);
await app.start();
Use app.dataSource to get the DataSource instance.
DataSourceOptions
dataSourceOptions
is a data source configuration you pass when you create a new DataSource
instance. Different RDBMS-es have their own specific options.
import { TypeNexus, TypeNexusOptions } from 'typenexus';
const options: TypeNexusOptions = {
dataSourceOptions: {
type: 'postgres',
host: process.env.POSTGRES_HOST || 'localhost',
port: 5432,
username: process.env.POSTGRES_USER || 'postgres',
password: process.env.POSTGRES_PASSWORD || 'wcjiang',
database: process.env.POSTGRES_DB || 'typenexus-base',
synchronize: true,
logging: true,
entities: ['dist/entity/*.js'],
// entities: [User],
},
}
;(async () => {
const app = new TypeNexus(3000, options);
await app.connect();
app.controllers([UserController]);
app.express.disable('x-powered-by');
await app.start();
})();
It can also be passed as a parameter inside the app.connect()
method:
await app.connect({ ... });
Entity
?Entity
is a class that maps to a database table (or collection when using Postgres
). You can create an entity by defining a new class and mark it with @Entity()
:
import { Entity, PrimaryGeneratedColumn, Column } from "typenexus"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
isActive: boolean
}
This will create following database table:
+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| firstName | varchar(255) | |
| lastName | varchar(255) | |
| isActive | boolean | |
+-------------+--------------+----------------------------+
import { DataSourceOptions } from 'typeorm';
import { OptionsUrlencoded, OptionsJson, OptionsText, Options } from 'body-parser';
import { SessionOptions } from "express-session";
export interface TypeNexusOptions {
port?: number;
/** Global route prefix, for example '/api'. */
routePrefix?: string;
/** DataSourceOptions is an interface with settings and options for specific DataSource. */
dataSourceOptions?: DataSourceOptions;
/** Create a session middleware */
session?: SessionResult | SessionCallback;
/** Node.js body parsing middleware. */
bodyParser?: {
/**
* Returns middleware that parses all bodies as a string and only looks at requests where the Content-Type header matches the type option.
*/
text?: OptionsText;
/**
* Returns middleware that parses all bodies as a Buffer and only looks at requests where the Content-Type header matches the type option.
*/
raw?: Options;
/**
* Returns middleware that only parses json and only looks at requests where the Content-Type header matches the type option.
*/
json?: false | OptionsJson;
/**
* Returns middleware that only parses urlencoded bodies and only looks at requests where the Content-Type header matches the type option
* Used for parsing request bodies in application/x-www-form-urlencoded format.
* @default `{extended:false}`
*/
urlencoded?: false | OptionsUrlencoded;
};
/** Node.js compression middleware. The following compression codings are supported: deflate | gzip */
compression?: false | CompressionOptions;
}
Example of parameter configuration:
new TypeNexus(3000, { routePrefix: 'api' });
@Req()
decorator injects you a Request object, and @Res()
decorator injects you a Response object. If you have installed typings, you can use their types:
import { Controller, Req, Res, Get } from 'typeorm';
import { Response, Request }from 'express';
@Controller()
export class UserController {
@Get('/users') // => GET /users
getAllUsers(@Req() request: Request, @Res() response: Response) {
return response.send('Hello response!');
}
@Get('/posts') // => GET /posts
getAllPosts(@Req() request: Request, @Res() response: Response) {
// some response functions don't return the response object,
// so it needs to be returned explicitly
response.redirect('/users');
return response;
}
}
You can use framework's request and response objects directly. If you want to handle the response by yourself, just make sure you return the response object itself from the action.
If you want to prefix all your routes, e.g. /api
you can use routePrefix option:
import { TypeNexus } from 'typenexus';
import { UserController } from './controller/User.js';
;(async () => {
const app = new TypeNexus(3033);
// 🚨 Be sure to put it in front of `app.controllers()`
app.routePrefix = '/api'
app.controllers([UserController]);
})();
You can also achieve the same effect by configuring routePrefix
in the parameters when instantiating TypeNexus
:
const app = new TypeNexus(3033, {
routePrefix: '/api'
});
You can prefix all specific controller's actions with base route:
import { Controller, Get } from 'typeorm';
@Controller('/api')
export class UserController {
@Get("/users/:id") // => GET /api/users/12
public async getOne() {}
@Get("/users") // => GET /api/users
public async getUsers() {}
// ...
}
@DSource()
decorator injects you a DataSource object.
import { Controller, Get, DSource, DataSource } from 'typenexus';
import { Response, Request }from 'express';
import { User } from '../entity/User.js';
@Controller('/users')
export class UserController {
@Get() // => GET /users
public async getUsers(@DSource() dataSource: DataSource): Promise<User[]> {
return dataSource.manager.find(User);
}
}
To inject request body, use @Body
decorator:
import { Controller, Post, Body } from 'typeorm';
type UserBody = { username: string; id: number; };
@Controller()
export class UserController {
@Post("/users") // => POST /users
saveUser(@Body() user: UserBody) {
// ...
}
}
To inject request body parameter, use @BodyParam
decorator:
import { Controller, Post, BodyParam } from 'typeorm';
type UserBody = { username: string; id: number; };
@Controller()
export class UserController {
@Post("/users") // => POST /users
saveUser(@BodyParam("name") userName: string) {
// ...
}
}
To inject request header parameter, use @HeaderParam
decorator:
import { Controller, Post, HeaderParam } from 'typeorm';
@Controller()
export class UserController {
@Post("/users")
saveUser(@HeaderParam("authorization") token: string) {
// ...
}
}
If you want to inject all header parameters use @HeaderParams()
decorator.
To inject query parameters, use @QueryParam
decorator:
import { Controller, Get, QueryParam } from 'typeorm';
type UserBody = { username: string; id: number; };
@Controller()
export class UserController {
@Get("/users")
public async getUsers(@QueryParam("limit") limit: number) {
// ....
}
}
If you want to inject all query parameters use @QueryParams()
decorator.
import { Controller, Get, QueryParams } from 'typeorm';
@Controller()
export class UserController {
@Get("/users")
public async getUsers(@QueryParams() query: any) {
// ....
}
}
You can use @Param
decorator to inject parameters in your controller actions:
import { Controller, Get, Param } from 'typeorm';
@Controller()
export class UserController {
@Get("/users/:id")
getOne(@Param("id") id: string) {}
}
If you want to inject all parameters use @Params()
decorator.
To get a cookie parameter, use @CookieParam
decorator:
import { Controller, Get, CookieParam, CookieParams } from 'typeorm';
@Controller()
export class UserController {
@Get("/users")
public async getUsers(@CookieParam("token") token: string) {
// ....
}
}
If you want to inject all header parameters use @CookieParams()
decorator.
To inject a session value, use @SessionParam
decorator:
@Get("/login")
savePost(@SessionParam("user") user: User, @Body() post: Post) {}
If you want to inject the main session object, use @Session()
without any parameters.
@Get("/login")
savePost(@Session() session: any, @Body() post: Post) {}
Express uses express-session
to handle session, so firstly you have to install it manually to use @Session
decorator. Here is an example of configuring Session, and you need to create a database table entity for Session as well:
import { TypeNexus, DataSourceOptions } from 'typenexus';
import { UserController } from './controller/User.js';
import { Session } from './entity/Session.js';
const options: TypeNexusOptions = {
// ...
dataSourceOptions: { ... },
session: {
secret: 'secret',
resave: false,
saveUninitialized: false,
repositoryTarget: Session,
typeormStore: {
cleanupLimit: 2,
// limitSubquery: false, // If using MariaDB.
ttl: 86400,
}
}
}
;(async () => {
const app = new TypeNexus(3001, options);
// ❶ Performs connection to the database.
await app.connect();
// OR:
// await app.connect(options.dataSourceOptions);
// ❷ 🚨 Please be sure to use it after `app.connect()`.
app.controllers([UserController]);
// ❸ Listen for connections.
await app.start();
})();
Here is the database table entity for Session:
// ./entity/Session.js
import { Column, Entity, Index, PrimaryColumn, DeleteDateColumn } from 'typeorm';
import { ISession } from 'connect-typeorm';
@Entity()
export class Session implements ISession {
@Index()
@Column('bigint', { transformer: { from: Number, to: Number } })
public expiredAt = Date.now();
@PrimaryColumn('varchar', { length: 255 })
public id = '';
@DeleteDateColumn()
public destroyedAt?: Date;
@Column('text')
public json = '';
}
authorization
featuresTypeNexus
comes with two decorators helping you to organize authorization in your application.
@Authorized
decoratorTo make @Authorized
decorator to work you need to setup special TypeNexus
options:
const app = new TypeNexus(3002, { ... });
await app.connect();
app.authorizationChecker = async (action: Action, roles: string[]) => {
// here you can use request/response objects from action
// also if decorator defines roles it needs to access the action
// you can use them to provide granular access check
// checker must return either boolean (true or false)
// either promise that resolves a boolean value
// demo code:
const token = action.request.query.token || action.request.body.token || (action.request.headers.authorization || '').replace(/^token\s/, '');
if (action.request.session.token !== token) return false;
const dataSource = action.dataSource;
const user = await dataSource.manager.findOne(User, {
where: { username },
select: ['username', 'id', 'roles'],
});
if (user && roles.find(role => user.roles.indexOf(role) !== -1)) return true;
// @ts-ignore
if (action.request.session.token === token) return true;
return false;
}
app.controllers([UserController]);
await app.start();
You can use @Authorized
on controller actions:
import { Controller, Authorized, Req, Res, Get } from 'typeorm';
import { Response, Request }from 'express';
@Controller()
export class UserController {
@Authorized('POST_MODERATOR') // you can specify roles or array of roles
@Post('/posts') // => POST /posts
create(@Body() post: Post, @Req() request: Request, @Res() response: Response) {
// ...
}
}
@CurrentUser
decoratorTo make @CurrentUser
decorator to work you need to setup special TypeNexus
options:
import { TypeNexus, Action } from 'typenexus';
import { UserController } from './UserController.js';
import { User } from './User.js';
;(async () => {
const app = new TypeNexus(3002, {
routePrefix: '/api',
developmentMode: false,
});
app.currentUserChecker = async (action: Action) => {
return new User(1, 'Johny', 'Cage');
}
app.controllers([UserController]);
await app.start();
})();
You can use @CurrentUser
on controller actions:
import { Controller, CurrentUser, Get } from 'typenexus';
import { User } from './User.js';
@Controller('/questions')
export class UserController {
@Get()
public async all(@CurrentUser() user?: User): Promise<any> {
return {
id: 1,
title: 'Question by ' + user.firstName,
};
}
}
If you mark @CurrentUser
as required and currentUserChecker
logic will return empty result, then TypeNexus
will throw authorization required error.
Global middlewares run before each request, always. To make your middleware global mark it with @Middleware
decorator and specify if it runs after or before controllers actions.
import { ExpressMiddlewareInterface } from 'typenexus';
import { Request, Response, NextFunction } from 'express';
@Middleware({ type: 'before' })
export class LoggingMiddleware implements ExpressMiddlewareInterface {
use(request: Request, response: Response, next: NextFunction): void {
console.log('do something...');
// @ts-ignore
request.test = 'wcj';
next();
}
}
To enable this middleware, specify it during typenexus
initialization:
import { TypeNexus } from 'typenexus';
import './LoggingMiddleware.js';
const app = new TypeNexus(3002, {
routePrefix: '/api',
developmentMode: false,
});
Or register with app.controllers()
.
import { TypeNexus } from 'typenexus';
import { LoggingMiddleware } from './LoggingMiddleware.js';
import { UserController } from './UserController.js';
const app = new TypeNexus(3002, {
routePrefix: '/api',
developmentMode: false,
});
app.controllers([UserController], [LoggingMiddleware]);
As always, thanks to our amazing contributors!
Made with contributors.
This package is licensed under the MIT License.