Classrouter
expressjs based routing system.
npm install classrouter
future
- Routing
- Param pipe
- Error handle
- Response filter
get start
@Controller({
name: 'home'
})
export class HomeController {
@Get(path :'/')
home(){
return new ViewResponse('home.html', {
message : 'welcome'
});
}
@Get(path :'/sitemap.xml')
sitemap(@BodyParam(VaidationPipe()) body:{user:string, pass:string}){
return new XmlResponse(...);
}
@Get(path :'/robots.txt')
robots(@BodyParam(VaidationPipe()) body:{user:string, pass:string}){
return new PlanResponse(...);
}
}
@Controller({
name: 'user', path: '/user'
})
export class UserController {
@Get(path :'/')
list(@QueryParam('limit', IntPipe() ) id:number){
return [...]
}
@Post(path :'/')
save(@BodyParam(VaidationPipe()) body:{user:string, pass:string}){
return {success : 'ok'}
}
}
async function startup() {
let app = express();
let factory = new ClassrouterFactory({
basePath: '/api',
logger: (l: string, m: string, o: any) => console.log(l, m, o),
routerBuilder: () => express.Router(),
controllers: [aController],
responseFilters: {
default: new JsonResponseFilter(),
filters: []
}
})
factory.build(app);
app.listen(3000, () => {
console.log('listen 3000');
});
}
startup().catch(console.log);
routing
supported HttpMethod. Get, Post, Put, Push, Delete, Head
supporting route:
- MethodRoute
- Classrouting
@Controller({name:'sample', path :'/sample'})
class SampleController {
@Get({path: '/foo/bar'})
sampleMethod(){
return { ... }
}
}
@Get({path: '/foo/bar', name:'sample'})
class SampleAction {
@Action()
doAction(){
return { ... }
}
}
@Controller({
name:'sample',
actions : [SampleAction]
})
class SampleController {
@Get({...})
foo(){
return { ... }
}
@Get({...})
bar(){
return { ... }
}
}
param
suporting param types. Query, Path, Body, Request, Header, Cookie;
@Get(filedName?: string | string[], ...pipe: IPipeTransform[])
fieldName empty : All params
fieldName defined : the field name resolved
@Get({name : 'sample', path: '/user/edit/:id'})
class SampleAction{
@PathParam('id')
idStr:string = 0
@PathParam()
appPathParams : any;
@PathParam('id', IntPipe())
idNum:number = ''
@RequestParam('session')
session:any;
@Action()
doAction(){
return {
idStr: this.idStr,
idNum: this.idNum,
appPathParams: this.appPathParams,
sessionName: this.session || this.session.name || 'no name',
}
}
}
@Controller({ ... })
class SampleController{
@Get({path: '/list'})
list( @QueryParam('limit', IntPipe()) limit ){
return [...]
}
@Post({path: '/save', before : [JsonbodyParser] })
Save(
@BodyParam('name') name ,
@RequestParam(new FlashPipe()) flash
){
try{
...
flash.message('save process success');
return 'success result';
}
catch(err:Error){
flash.message('save process fail.' + err.message );
return 'fail result';
}
}
}
pipe
class IntPipe implements IPipeTransform {
transform(idStr: string) {
return parseInt(idStr);
}
}
class ModelPipe implements IPipeTransform {
constructor (private EntityType: IModel){
}
async transform(idNum: number) {
let entity = await this.EntityType.findById(idNum);
if(entity) {
return entity;
}
throw new Error('not found Entity id');
}
}
@QueryParam('id', new IntPipe(), new ModelPipe(UserEntity))
Error handle
class SampleAction {
@Action({errorHandle : 'onError'})
doAction(){
throw new Error("test error")
}
onError(err:any){
return new ViewResponse('error.html',{
title : 'Error page',
error : err
});
}
}
class FooError {}
class BaaError {}
@Controller({ name:'sample', errorHandle : 'onError' })
class SampleController {
@Get({ path: '/', errorHandle : 'sampleError' })
sample(){
throw new Error();
}
@Get({ })
foo(){
throw new FooError();
}
@Get({ })
baa(err:any){
throw new BaaError();
}
onError(err:any){
return ...
}
sampleError(err:any){
}
@ErrorHandle({ instanceOf: FooError })
fooError(err:any){
return ...
}
@ErrorHandle({ when: (err) => err instanceof BaaError })
baaError(err:any){
return ...
}
}
response filter
interface IFilterParam {
actionResult: any
expressRes: express.Response
expressReq: express.Request
handled: boolean
}
interface IResponseFilter {
filter(param: IFilterParam): void | Promise<void>
}
export class RedirectResponse {
statusCode: number
constructor(public uri: string, temp: boolean = true) {
this.statusCode = temp ? 302 : 301
}
}
export class RedirectResponseFilter implements IResponseFilter {
filter(params: IFilterParam) {
let { actionResult, expressRes } = params;
if (actionResult instanceof RedirectResponse) {
expressRes.redirect(actionResult.statusCode, actionResult.uri);
params.handled = true;
}
}
}
&Controller( ... )
class SampleController(){
@Get({path: '/edit/:id'})
edit(){
...
if( checkId ) {
return new ViewResponse( ... );
}else{
return new RedirectResponse(`/error`, false);
}
}
@Post({path:'/save'})
doSave(){
...
if(isError) {
return new RedirectResponse(`/edit/${id}`);
}
...
}
}
sample React response
export class ReactResponseFilter implements IResponseFilter {
async buildAppContext(req: express.Request): Promise<IAppContext> {
let flash = null;
if (req.session) {
flash = req.session.flash;
delete req.session.flash;
}
return {
rqPath: req.originalUrl,
expressReq: req,
flash,
}
}
async filter(param: IFilterParam) {
let { actionResult, expressRes, expressReq } = param;
if (React.isValidElement(actionResult)) {
let appContext = await this.buildAppContext(expressReq);
ReactDOM.renderToNodeStream(<AppContext.Provider value={appContext}>{actionResult} </AppContext.Provider>)
.pipe(expressRes);
param.handled = true;
}
}
}
@Get( ... )
class SampleAction {
@Action()
doAction(@QueryParam('name') name:string ){
return <div>
name : {{name}}
</div>
}
}