How to organize a project in Ode

Asked

Viewed 1,210 times

10

I would like some idea of how I could organize a project in Node.js, currently the files are this way:

- router.js
- controller
    L controllerUser.js
    L controllerAuth.js
    L ...
- service
    L mongodbConnection.js
    L serviceUser.js
    L ...

In service is basically a CRUD (for now):

const mongodbConnection = require('./mongodbConnection.js');

//Find document of collection user by propriety name
module.exports.findByName = async function(object) {
    let connection = await mongodbConnection.connection(collection);

    let select = await connection.find(object);

    return select.toArray();
}

In the service the connection to the database, in the case, mongodb, is through a module:

const mongoClient = require('mongodb').MongoClient;

module.exports.connection = async function(collection) {
    try {
        //Connect MongoDB
        let database = await mongoClient.connect('mongodb://localhost:27017');
        console.log('Connected successfully');

        //Select and return database connection
        return database.db('dataPOA').collection(collection);
    } catch (error) {
        console.log(error);
        return error;
    }
}
  • Starting a new connection at the beginning of each function is correct? It looks like a lot of code repetition, has how to improve?
  • How to close this connection? Or should I leave it open anyway?
  • It would not be prudent to place this code that performs functions in the bank within a try catch?

3 answers

8


What I use is more or less this here:

- src/
    - main.js
    - modules/
        - [controllers]
        - [models]
        - database.js
        - [script variados: util.js, http-server.js, socket-server.js, etc]
    - views/
        - [templates: mustache|handlebars|whatever]
    - routes/
        - user.js (/user routes)
        - admin.js (/admin routes)
        - etc
- test/
    - [units]
    - [acceptances]
    - etc
- util/
    - [script empacotador do app]
    - [scripts variados]
- package.json
- [yarn.lock|package-lock.json]

In database.js we will have the following:

'use strict';

const mongoClient = require('mongodb').MongoClient;

module.exports.isReady = new Promise(async (resolve, reject) => {
    try {
        //Connect MongoDB
        let database = await mongoClient.connect(
            'mongodb://localhost:27017', {useNewUrlParser: true}
        );
        console.log('Connected successfully');

        //Select and return database connection
        let db = database.db('dataPOA');

        module.exports.conn = db;
        resolve();
    }
    catch (err) {
        reject(err);
    }
});

The idea is you open a permanent connection at the beginning of the application. This connection will never be closed, you will reuse it throughout your app.

As the connection establishment is asynchronous we need a way to identify that the connection is ready for use and that we can start our application, hence the use of the Promise isReady (in module.exports.isReady). Promise will only be resolved when the connection has been established.

Keeping in mind that the starting point of the application is the file main.js, in it we’ll have something like this:

'use strict';
const DB = require('./modules/database');

DB.isReady.then(async () => {
    console.info('Datbase is ready');

    // let server = HttpServer();
    // socket server

    // um arquivo qualquer que faz algo com a db
    require('./modules/dosomething');

    // mais requires
    // etc
}).catch((err) => {
    console.error('damn', err);
    process.exit(1);
});

modules/dosomething is any file, in it we have an example of how you will use the pre-set connection:

'use strict';
// dosomething.js
const DB = require('./database');

const collection = DB.conn.collection('maybeMongodbIsNotTheBestChoice');

// obs poderíamos usar async/await aqui sem problemas 
// caso isso estivesse encapsulado em uma funćão async
collection.insertMany([
    {name: 'afasdfasdfa'},
    {name: 'erqwqwerwqerwqer'}
]).then(() => {
    return collection.find({}, {limit: 5}).toArray();
}).then((res) => {
    console.info(res);
});

And that’s basically it, whenever you want to use the database you will require modules/database.js and use [DB].conn. The crucial difference from this model to yours is that here DB.conn.[algum método] (DB.conn.collection('minhacollection')) nay establishes a new connection with the bank, only reuses the one made in main.js at the first require of modules/database.js. In his example all time we called mongodbConnection.collection('minhacollection') we are establishing a new connection with the bank and the mongodb documentation itself already recommends avoiding that Pattern.

I also provided an example on https://github.com/BrunoRB/so-pt-304009-como-organizar-um-projeto-em-node, just clone the project, run npm|Yarn install inside the main folder and run node src/main.js.

Finally I hope that it is clear that although the example uses mongodb this template is not restricted to it. In dbs like mysql or sqlite you can (and I recommend) use a similar pattern, with the appropriate changes of course. For example: using mysql you not only want to expect that the connection has been established, but also that all your tables, views, triggers, etc., have been created before you start using the connection. For that we would need to adapt the isReady so that he just calls the resolve after all their CREATE TABLE (and the like) have completed.

  • I come from PHP and it’s kind of strange to think about creating a connection and always keep it open. And the use of try catch, recommends or whatever?

  • How do you use this connection on your controllers or routers? Import the database module again?

  • I understand your point, it is really exotic to come from a language where you instantiate a connection to each new request and come across Ode and have the connection always open, but this is normal. About try...catch, the app has no use without the DB, so you can put the Try...catch, log the error and give a process.exit(1) no catch, or just let it explode and fail. at all times import the file database.js, where she settled, in case the: const connection = require('./modules/database.js');

  • Ok. I still don’t understand very well, in case I have the files inside the folder service, where I have the functions that access the bank, I need that variable there, how does it get there? could give an example of a bank access function?

  • The files of service/ would const connection = require('./../modules/database.js');, having access to the connection object that was already created when you started the app running something like node main.js. I’m a little out of time right now, but if you still need it tomorrow night, I’ll create a complete example for you to see the idea in operation.

  • That’s more or less what I do, but it seems that this way he’s looking for a new connection in each require

  • In what you do a function is exported, this function instantiates a new Connection every time it is invoked. So if you have const c = require('mongodbConnection.js').connection(collection), This generates a new connection because it runs all that function block that you exported. In what I suggested not, the connection would be created in the database.js already when it is first required, requires subsquentes do not create a new connection by in Node require() is curly. The whole difference is in you exporting a function that generates a Connection, where I suggest exporting to Connection directly.

  • If you have please add that example please

  • @Guilhermecostamilam see if it’s clearer now, I’ve added a (almost) complete example using mongodb.

Show 4 more comments

1

Not long ago I met a framework with MVC standard for Nodejs. It is called Adonis.
It is extremely organized and inherits a bit of Laravel style.

The only harm is that it does not support Mongodb, since it works with SQL in a standard known as active record. However, I don’t see this as a problem.

  • In this case, yes, it is a problem, but it does not invalidate the answer. If you can add why it is organized and why the framework works

1

Browser other questions tagged

You are not signed in. Login or sign up in order to post.