Alosaur 🦖
Alosaur - Deno web framework 🦖.
- Area - these are the modules of your application.
- Controller - are responsible for controlling the flow of the application execution.
- Middleware - provide a convenient mechanism for filtering HTTP requests entering your application.
- Hooks - middleware for area, controller and actions with support DI. Have 3 life cyclic functions:
onPreAction, onPostAction, onCatchAction
- Decorators - for query, cookie, parametrs, routes and etc.
- Dependency Injection - for all controllers by default from
microsoft/TSyringe
(more about alosaur injection). - Render pages any template render engine More
Features roadmap
Q4 2020 – Oct-Dec
- WebSocket example
- SSE
- OpenAPI type reference
- microservice connector with WASM
- implement passport strategy
Examples
- Basic + OpenAPI v3 generator (Swagger)
- CORS middleware
- SPA middleware
- Static content middleware
- Database PostgreSQL
- Template render: Dejs, Handlebars, Angular, Eta
- Body transform, validator
- DI
- Docker
- Hooks
Simple example
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
TODO
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
OpenAPI v3
AlosaurOpenApiBuilder.create(settings)
.addTitle("Basic Application")
.addVersion("1.0.0")
.addDescription("Example Alosaur OpenApi generate")
.addServer({
url: "http://localhost:8000",
description: "Local server",
})
.saveToFile("./examples/basic/api.json");
Generate OpenAPI file:
deno run -A --config ./src/tsconfig.lib.json examples/basic/openapi.ts
Middleware
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());
WebSocket middleware example
Use context.response.setNotRespond()
for return the rest of the requests
export class WebsocketMiddleware implements PreRequestMiddleware {
onPreRequest(context: Context) {
const { conn, r: bufReader, w: bufWriter, headers } =
context.request.serverRequest;
acceptWebSocket({
conn,
bufReader,
bufWriter,
headers,
})
.then(ChatHandler) // execute chat
.catch(async (e) => {
console.error(`failed to accept websocket: ${e}`);
await context.request.serverRequest.respond({ status: 400 });
});
context.response.setNotRespond(); // It is necessary to return the rest of the requests by standard
}
}
Hooks
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 ``;
}
}
Global error handler
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();
});
Action outputs: Content, View, Redirect
There are 3 ways of information output
- Content similar
return {};
by default Status 200 OK - View uses with template engine,
return View("index", model);
- Redirect and RedirectPermanent status 301,302
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
Render pages
Alosaur can suppport any html renderer. All you have to do is define the rendering function in the settings. For example Dejs, Handlebars, Angular, Eta
// 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,
}
)
Transformers and validators
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],
});
// add transform 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) {
}
Custom Decorators
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";
}