Speedy, all-in-one web application framework
Before the app can run, Config
object should be initialized with the init
function. This function returns a promise that if fulfilled, the app can be created.
The argument to the init
function should be an object with the following format:
{
amqp: object({
host: string().required(),
password: string().optional().default('guest'),
port: number().optional().default(5672),
protocol: string().optional().default('amqp'),
username: string().optional().default('guest'),
}).optional(),
app: object({
nodeEnv: string().optional().default('development'),
}).optional().default({
nodeEnv: 'development',
}),
authentication: object({
secretKey: string().required(),
tokenLifeTime: number().optional().default(30 * 24 * 60 * 60 * 1000),
}).required(),
dataStore: object({
prefix: string().empty('').optional().default(''),
type: string().optional().default('redis'),
}).optional().default({
prefix: '',
type: 'redis',
}),
redis: object({
host: string().required(),
password: string().empty('').optional().default(''),
port: number().optional().default(6379),
protocol: string().empty('').optional().default(''),
username: string().empty('').optional().default(''),
}).required(),
}
For example:
Config.init({
amqp: {
host: '127.0.0.1',
},
authentication: {
secretKey: 'abcdefg',
},
redis: {
host: '127.0.0.1',
},
});
After Config
object is initialized and the returning promise is fulfilled, the app can be created.
const app = new App(options);
options
for the app includes an string name
, an optional http
server options, and an optional rpc
server options. http
server options should be in the following format.
http: {
host: string;
port: number;
protocol?: 'http' | 'https';
}
rpc
server options should be in the foloowing format:
rpc: {
queueNames: string[];
}
An example for creating an app is as follows:
const app = new App({
http: {
host: '127.0.0.1',
port: 9000,
protocol: 'http',
},
name: 'testApp',
rpc: {
queueNames: ['local_test_queue'],
},
});
To run the app, run
method should be called. For example:
app.run();
To add a route to all defined servers, addRoutes
method on the app instance should be called with an object implementing the following interface. This methods accepts both a single route options objects and array of them.
interface IRouteOptions {
name: string;
description: string;
method: RouteMethod;
path: string;
controller: (...args: any[]) => Promise<any>;
authentication?: IAuthenticationOptions;
files?: boolean;
payload?: (request: express.Request) => any;
validate?: Joi.SchemaMap;
rateLimit?: IRateLimiterOptions;
cache?: ICacherOptions;
}
Route method accepts values from RouteMethod
enum with the following definition:
enum RouteMethod {
Get,
Post,
Put,
Patch,
Delete,
}
controller
must be an async function (returns a promise) and it will be called with the object returned from the payload
function.
authentication
object should implement the following interface:
files
item is a boolean value, which if set to true, adds files
array to the request object in payload
function. The array will contain attached files with the following fields: fieldname
, originalname
, encoding
, mimetype
, size
, and buffer
.
interface IAuthenticationOptions {
customAuthenticator?: (token: IAuthenticationToken) => boolean;
renewToken?: boolean;
roles: string[];
}
rateLimit
object should implement the following interface:
interface IRateLimiterOptions {
duration: number;
allowedBeforeDelay: number;
maximumDelay: number;
allowedBeforeLimit: number;
message?: string;
keyGenerator?: (req: express.Request | IRpcRequest) => string;
key: string;
}
cache
object should implement the following interface:
interface ICacherOptions {
expire: number;
authBased: boolean;
}
Below is an example of adding a route:
app.addRoutes({
authentication: {
renewToken: true,
roles: ['user'],
},
cache: {
authBased: false,
expire: 10,
},
controller: async ({ files, id, name }) => {
return {
id,
message: `Hello ${name}! ${files.length} files received.`,
timestamp: Date.now(),
};
},
description: 'Get user information',
files: true,
method: RouteMethod.Post,
name: 'get_user',
path: '/user/:id',
payload: (req) => ({
files: req.files,
id: req.params.id,
name: req.body.name,
}),
rateLimit: {
allowedBeforeDelay: 10,
allowedBeforeLimit: 20,
duration: 60,
key: '',
maximumDelay: 30 * 1000,
},
validate: {
files: Joi.array().required(),
id: Joi.string().required(),
name: Joi.string().required(),
},
});
To communicate between services, RPC requests can be sent using the send
method of the RpcSender
class. The method is an async function that returns a promised, which if fulfilled, contains the response for the request. The argument for the send
method should implement the following interface:
IRpcSenderRequest {
service: string;
name?: string;
method?: RouteMethod;
path?: string;
authenticationToken?: string;
payload?: any;
}
An example of sending an RPC request to a service called local_test_queue
and a processor (e.g., route) called local_test_queue
:
const response = await RpcSender.send({
authenticationToken: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6Ikprg0ofdyjjQbS1HEPr3xhM',
name: 'create_user',
payload: {
id: '123',
name: 'Dear User',
},
service: 'user_management',
});