Alosaur - Deno web framework 🦖.
onPreAction, onPostAction, onCatchAction
microsoft/TSyringe
(more about alosaur injection).app.ts:
import { Controller, Get, Area, App } from 'https://deno.land/x/alosaur@v0.21.1/mod.ts';
@Controller() // or specific path @Controller("/home")
export class HomeController {
@Get() // or specific path @Get("/hello")
text() {
return 'Hello world';
}
}
// Declare module
@Area({
controllers: [HomeController],
})
export class HomeArea {}
// Create alosaur application
const app = new App({
areas: [HomeArea],
});
app.listen();
tsconfig.app.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
And run
deno run --allow-net --allow-read --config ./tsconfig.app.json app.ts
Add render views: Dejs and Handlebars
Add return value JSON
Add decorators:
@Area
@QueryParam
@Param
param from url: /:id
@Body
@Cookie
@Req
@Res
@Middleware
with regex route
@UseHook
for contoller and actions
Support create custom decorators with app metadata
Add middleware
Add static middleware (example: app.useStatic)
Add CORS middleware
Add SPA middleware
Add DI
Add std exceptions
Add CI with minimal tests.
Add OpenAPI v3 generator (see /examples/basic/openapi.ts)
Add OpenAPI type reference
Add Hooks example
Add WebSocket
Add SRE
Add validators example class-validator
Add microservice connector with WASM
Add benchmarks
Transfer to Alosaur github organization
Add docs and more examples
Plugins & modules
Add Angular render engine
Add CLI with schematics (https://github.com/alosaur/alosaur-schematics)
Examples
Add basic example
Add DI example
Add static serve example
Add Dejs view render example
Add example with SQL drivers (PostgreSQL)
Add basic example in Docker container
Add WebSocket example
Add example with WASM
Example in examples/basic/openapi.ts
Generate OpenAPI file:
deno run -A --config ./src/tsconfig.lib.json examples/basic/openapi.ts
You can create middleware and register it in area or all application layer.
@Middleware(new RegExp('/'))
export class Log implements MiddlewareTarget<TState> {
date: Date = new Date();
onPreRequest(context: Context<TState>) {
return new Promise((resolve, reject) => {
this.date = new Date();
resolve();
});
}
onPostRequest(context: Context<TState>) {
return new Promise((resolve, reject) => {
console.log(new Date().getTime() - this.date.getTime());
resolve();
});
}
}
Register in app settings
const settings: AppSettings = {
areas: [HomeArea, InfoArea],
middlewares: [Log],
};
or in app
const app = new App(settings);
app.use(/\//, new Log());
Hooks - middleware for area, controller and actions with supports DI container.
Hook in Alosaur there are three types: onPreAction, onPostAction, onCatchAction
.
type PayloadType = string; // can use any type for payload
type State = any;
export class MyHook implements HookTarget<State, PayloadType> {
// this hook run before controller action
onPreAction(context: Context<State>, payload: PayloadType) {
// you can rewrite result and set request immediately
context.response.result = Content({error: {token: false}}, 403);
context.response.setImmediately();
// if response setted immediately no further action will be taken
};
// this hook run after controller action
onPostAction(context: Context<State>, payload: PayloadType) {
// you can filtered response result here
};
// this hook run only throw exception in controller action
onCatchAction(context: Context<State>, payload: PayloadType) {
};
}
uses:
@UseHook(MyContollerHook) // or @UseHook(MyHook, 'payload') for all actions in controller
@Controller()
export class HomeController {
@UseHook(MyHook, 'payload') // only for one action
@Get('/')
text(@Res() res: any) {
return ``;
}
}
Errors that haven’t been caught elsewhere get in here
const app = new App(
// app settings
);
// added global error handler
app.error((context: Context<any>, error: Error) => {
context.response.result = Content("This page unprocessed error", (error as HttpError).httpCode || 500);
context.response.setImmediately();
});
There are 3 ways of information output
https://github.com/alosaur/alosaur/tree/master/src/renderer
return {};
by default Status 200 OKreturn View("index", model);
return Redirect('/to/page')
return {}; // return 200 status
// or
return Content("Text or Model", 404); // return 404 status
// or
return View("page", 404); // return 404 status
Alosaur can suppport any html renderer. All you have to do is define the rendering function in the settings. For example Dejs, Handlebars, Angular
// Handlebars
...
// Basedir path
const viewPath = `${Deno.cwd()}/examples/handlebars/views`;
// Create Handlebars render
const handle = new Handlebars();
app.useViewRender({
type: 'handlebars',
basePath: viewPath,
getBody: async (path: string, model: any, config: ViewRenderConfig) => await handle.renderView(path, model),
});
...
Handlebars support custom config, more about handlebars for deno
new Handlebars(
{
baseDir: viewPath,
extname: '.hbs',
layoutsDir: 'layouts/',
partialsDir: 'partials/',
defaultLayout: 'main',
helpers: undefined,
compilerOptions: undefined,
}
)
You can use different transformers
For example class-validator
and class-transformer
for body
post.model.ts:
import validator from "https://jspm.dev/class-validator@0.8.5";
const { Length, Contains, IsInt, Min, Max, IsEmail, IsFQDN, IsDate } =
validator;
export class PostModel {
@Length(10, 20)
title?: string;
@Contains("hello")
text?: string;
@IsInt()
@Min(0)
@Max(10)
rating?: number;
@IsEmail()
email?: string;
}
app.ts
import validator from "https://jspm.dev/class-validator@0.8.5";
import transformer from "https://jspm.dev/class-transformer@0.2.3";
import { App, Area, Controller, Post, Body } from 'https://deno.land/x/alosaur/mod.ts';
import { PostModel } from './post.model.ts';
const { validate } = validator;
const { plainToClass } = transformer;
// Create controller
@Controller()
export class HomeController {
@Post('/')
async post(@Body(PostModel) data: PostModel) {
return {
data,
errors: await validate(data)
}
}
}
// Declare controller in area
@Area({
controllers: [HomeController],
})
export class HomeArea { }
// Create app
const app = new App({
areas: [HomeArea],
});
// added tranform function
app.useTransform({
type: 'body', // parse body params
getTransform: (transform: any, body: any) => {
return plainToClass(transform, body);
}
})
// serve application
app.listen();
You can also use just a function instead of a transformer.
function parser(body): ParsedObject {
// your code
return body;
}
...
@Post('/')
post(@Body(parser) data: ParsedObject) {
}
You can add any decorator and put it in a DI system.
Example with hooks:
import {
Content,
Context,
HookTarget,
BusinessType,
getMetadataArgsStorage,
container
} from "https://deno.land/x/alosaur/mod.ts";
type AuthorizeRoleType = string | undefined;
/**
* Authorize decorator with role
*/
export function Authorize(role?: AuthorizeRoleType): Function {
return function (object: any, methodName?: string) {
// add hook to global metadata
getMetadataArgsStorage().hooks.push({
type: methodName ? BusinessType.Action : BusinessType.Controller,
object,
target: object.constructor,
method: methodName,
instance: container.resolve(AutorizeHook),
payload: role,
});
};
}
export class AutorizeHook implements HookTarget<unknown, AuthorizeRoleType> {
onPreAction(context: Context<unknown>, role: AuthorizeRoleType) {
const queryParams = getQueryParams(context.request.url);
if (queryParams == undefined || queryParams.get("role") !== role) {
context.response.result = Content({ error: { token: false } }, 403);
context.response.setImmediately();
}
}
}
Then you can add anywhere you want. For example action of controller:
// ..controller
// action
@Authorize("admin")
@Get("/protected")
getAdminPage() {
return "Hi! this protected info";
}
Author: alosaur
GitHub: https://github.com/alosaur/alosaur
#deno #nodejs #javascript