npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

classrouter

v4.4.2

Published

typescript expressjs class router

Downloads

11

Readme

Classrouter

expressjs based routing system.

npm install classrouter

future


  • Routing
  • Param pipe
  • Error handle
  • Response filter

get start


// sample controller
// home.controller.ts

@Controller({
    // name param requared. use  the  param. Swagger json. 
    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(...);
    }
}
// sample Rest API
// user.controller.ts

@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'}
   }
}
// sample server
// src/server.ts

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: [/*XML, Plan, File, */] // your custom response 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
    // routing path:
    // <basePath>/<MainController-path>/<ChildController-path>/<Action-Path>
// method route
// call addr : [GET]<basePath>/sample/foo/bar
@Controller({name:'sample', path :'/sample'})
class SampleController {

    @Get({path: '/foo/bar'})
    sampleMethod(){
        return { ... }
    }
}
// class route
// call addr : [GET]<basePath>/foo/bar
@Get({path: '/foo/bar', name:'sample'})
class SampleAction {

    
    @Action() // register action method
    doAction(){
        return { ... }
    }
}

@Controller({
    name:'sample',
    actions : [SampleAction] //<- register class action
})
class SampleController {

    /**
     *  classAction with methodAction supported
     */
    @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') // "id" - path param name
    idStr:string = 0 // default value = 0;

    @PathParam() // not defined param name
    appPathParams : any; // all path params.  {id:string}

    @PathParam('id', IntPipe()) // intPipe is  string to number convert
    idNum:number = '' // default value = '';

    @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{

    // call addr : <basePath>/list?limit=10
    @Get({path: '/list'})
    list( @QueryParam('limit', IntPipe()) limit ){
        return [...]
    }

    @Post({path: '/save', before : [JsonbodyParser] })
    Save( 
        @BodyParam('name') name ,
        @RequestParam(new FlashPipe()) flash 
    ){

        try{
            // save process
            ...

            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))

// id string ==> id number ==> UserEntity find by id param

Error handle

// class action 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
        });
    }

}
// method action error handle

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();
    }

    // contoller all action Error handle
    onError(err:any){
        return ...
    }

    // only "sample" method action Error handle
    sampleError(err:any){

    }


    // controller all action Error handle. filtering instanceOf FooError
    @ErrorHandle({ instanceOf: FooError })
    fooError(err:any){
        return ...
    }

    // all action handle. filtering function
    @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;  // this response filter handled.
        }
    }
}

// use
&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}`);
        }

        // do save process
        ...
    }
}

// register response filter
// classrouterFactory.setupResonsefilter(new JsonResponseFilter())

sample React response

// used by react serverside rendering. 
// using the react side render
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>
    }
}



// register response filter
// classrouterFactory.setupResonsefilter(new ReactResponseFilter())