Node.js modular folder structure and best coding practice – part 2 – Reusable components.

Reusable components – common module usage.

What is common module.

We require certain reusable components to save our development time, code length, and code maintenance.

Isn’t it cool!

Github Repository.

Boilerplate of MERN stack with Typescript 

https://github.com/ronakamlani/ChatAppBackend

Let’s get started


Router Flow

Goal Statement:

  1. Build a managable router, we achieved by creating a seperate router by module and role.
  2. Also, we are using passport, so in the app need it to configure with passport and use that object all over the app. We have achieve this think by pass the app object to the router method. initRouters(app) //at src/app.ts
We have no organized mechanism to handle our system right :p
Now, it’s easy right!

Solution

1. Router flow started by app.ts because we have.
///src/app.ts
import { initRoutes } from './modules/index';
initRoutes(app);

2. Router master file, responsible to manage a different kind of high categories path, let’s say in the future we need to add a new API version, in that case, we just need to configure new paths like the below snap code, and boom.
//src/modules/index.ts
export const initRoutes = (app:express.Application)=>{
    app.use('/api/1.0',apiRouter(app));
}

Let’s say we are looking to add a new public view, at https://YOURDOMAIN.com/anyPath

In that case, index.ts will look like this.

//src/modules/index.ts
export const initRoutes = (app:express.Application)=>{
    app.use(publicViews(app));
    //Rest of app.use(...)
}

3. Now we have another layer of the path, I call it module master routers.
I have built for API, you can create for publicView.router.js if you want.
//modules/api.routers.ts
export const apiRouter = (app:express.Application)=>{
  const routers:express.Router = express.Router();


    routers.use('/client',(new ClientRoutes(app)).configureRoutes() );

    routers.use(error500); //I used this to handler here only because I can handle JSON error at path "/api/*" pattern.

    return routers;
}
4. Final Router layer, where we will define the inner path of the module, let’s say login, logout, etc.
//We want to use passport so I have extends CommonRouterconfig, if you want you can create a seperate CommonApiRoutersConfig based upon your business requirement. 
export class AuthRoutes extends CommonRoutesConfig {
  configureRoutes() {
    const authRouter = express.Router();
     
     /*
     1. Here, we have sort of 4 responsibities, I keep it seperate as possible, so if needed I can test it and reuse it.
     2. authorizationValidationRule: This will validate user input.
     3. bodyValidationMiddleware : Common pattern for user input error, let's say user forgot to add username or password, this method will return in the uniform pattern.
     4. this.passport.authenticate('basic', { session: false }) : This function will check your username,password.
     5. this.passport.authenticate('oauth2-client-password', { session: false }): This function will check user input client credentials.
     6. tokenProcess : is a part of oauth2 and other modules, responsible to response back error or access_token and refresh_token.
     */
     authRouter.post('/token', 
         authorizationValidationRule,
         bodyValidationMiddleware,
         this.passport.authenticate('basic', { session: false }),
         this.passport.authenticate('oauth2-client-password', { session: false }),
         tokenProcess
       );
     
     //other authRouters...
  }
}


Database Connectivity

You can choose your favorite database, I choose mongoose here, just configure with 2 simple methods connect and disconnect.
//https://github.com/ronakamlani/ChatAppBackend/blob/master/src/modules/common/services/mongoose.service.ts
//src/modules/common/services/mongoose.service.ts
class MongooseService {
   ....
 connectWithRetry = async() => {
        log('Attempting MongoDB connection (will retry if needed)',process.env.MONGO_URI);

        this.mongoMemoryServer =  await MongoMemoryServer.create();
        const uri = process.env.NODE_ENV !== 'test' ? process.env.MONGO_URI || '': this.mongoMemoryServer.getUri();
            
        this._connetMongoUri(uri)
        .then(() => {
            log('MongoDB is connected');
        })
        .catch((err) => {
            const retrySeconds = 5;
            log(
                `MongoDB connection unsuccessful (will retry #${++this
                    .count} after ${retrySeconds} seconds):`,
                err
            );
            setTimeout(this.connectWithRetry, retrySeconds * 1000);
        });

    };
    
    async dbDisconnect(){
        if(this.mongoMemoryServer){
            await mongoose.connection.dropDatabase();
            await mongoose.connection.close();
            await this.mongoMemoryServer.stop();
        }
    }
    ...
}

Error Handling

Levels

  • User input errors, error code : 400 covered by //BodyValidationMiddleware
  • Request, Response error : You have to handle such error by try and catch block in service or controller.
  • Uncached error : This is the common error when system dont have a specific error. We gone cover this right now.

Uncached error:

This is the snapshot to describe when this function going to call.

class AuthController extends CommonController{
    async authorizationCodegenerator(req:express.Request,res:express.Response, next : express.NextFunction    
    ){ 
     try{
      }
     catch(err){
       //I am passing this error as unhandled error to app, but we already configured api error in 
       //src/modules/api.routers.ts please, follow the next snap code.
       return next(err);
     }
   }
}

//master/src/modules/api.routers.ts
export const apiRouter = (app:express.Application)=>{
  
    const routers:express.Router = express.Router();

    routers.use(error500);

    return routers;
}

Conclusion

The folder structure totally depends on your project definition and goal statement, this is what I am thinking is a generic structure you can save your time by creating some reusable components.

If you like my block, please share it with your team, friends and don’t forget to comment.

Thanks for your visit guys,

Cheers.

1 thought on “Node.js modular folder structure and best coding practice – part 2 – Reusable components.

Leave a Reply

Your email address will not be published. Required fields are marked *