Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

create models in expressjs

I have an express app that gets its data from an external API

api.com/companies (GET, POST)
api.com/companies/id (GET, PUT)

I want to create a model to make the code easier to maintain as you can see I´m repeating a lot of code here.

router.get('/companies', function(req, res, next) {

    http.get({
        host: 'http://api.com',
        path: '/companies'
    }, function(response) {
        var body = '';
        response.on('data', function(d) {
            body += d;
        });
    });

    res.render('companies', {data: body});
});

router.get('/companies/:id', function(req, res, next) {

    http.get({
        host: 'http://api.com',
        path: '/companies/' + req.params.id
    }, function(response) {
        var body = '';
        response.on('data', function(d) {
            body += d;
        });
    });

    res.render('company', {data: body});
});

How can I do that?

like image 793
handsome Avatar asked Oct 19 '16 04:10

handsome


People also ask

Is ExpressJS outdated?

Express has not been updated for years, and its next version has been in alpha for 6 years. People may think it is not updated because the API is stable and does not need change. The reality is: Express does not know how to handle async/await .

Is Express better than NestJS?

Since ExpressJS doesn't follow MVC, there's no proper structure which makes the application inefficient and less optimized. NestJS becomes a better choice for developers as it is clearly based on an architecture with components like modules, controllers, and providers.


3 Answers

First of all: http.get is asynchronous. Rule of thumb: When you see a callback, you are dealing with an asynchronous function. You can not tell, if the http.get() and its callback will be completed when you terminate the request with res.render. This means that res.render always needs to happen within the callback.

I am using ES6 syntax in this example.

// request (https://github.com/request/request) is a module worthwhile installing. 
const request = require('request');
// Note the ? after id - this is a conditional parameter
router.get('/companies/:id?', (req, res, next) => {

    // Init some variables
    let url = ''; 
    let template = ''

    // Distinguish between the two types of requests we want to handle
    if(req.params.id) {
        url = 'http://api.com/companies/' + req.params.id;
        template = 'company';
     } else {
        url = 'http://api.com/companies';
        template = 'companies';
     }

    request.get(url, (err, response, body) => {
        
        // Terminate the request and pass the error on
        // it will be handled by express error hander then
        if(err) return next(err);
        // Maybe also check for response.statusCode === 200

        // Finally terminate the request
        res.render(template, {data: body})
    });

});

Regarding your 'model' question. I would rather call them 'services' as a model is some collection of data. A service is a collection of logic.

To create a company service module could do this:

// File companyService.js
const request = require('request');

// This is just one of many ways to encapsulate logic in JavaScript (e.g. classes)
// Pass in a config that contains your service base URIs
module.exports = function companyService(config) {
    return {
        getCompanies: (cb) => {
            request.get(config.endpoints.company.many, (err, response, body) => {
                return cb(err, body);
            });
        },
        getCompany: (cb) => {
            request.get(config.endpoints.company.one, (err, response, body) => {
                return cb(err, body);
            });
        },
    }
};


// Use this module like
const config = require('./path/to/config');
const companyService = require('./companyService')(config);

// In a route
companyService.getCompanies((err, body) => {
    if(err) return next(err);

    res.render(/*...*/)
});
like image 185
DanielKhan Avatar answered Oct 20 '22 08:10

DanielKhan


There is no need to have multiple routes in this case! You can make use of an optional parameter using ?. Take a look at the following example:

router.get('/companies/:id?', function(req, res, next) {
    var id = req.params.id;

    http.get({
        host: 'http://api.com',
        path: '/companies/' + id ? id : ""
    }, function(response) {
        var body = '';
        response.on('data', function(d) {
            body += d;
        });
    });

    res.render('companies', {data: body});
});

The bit of code here:

path: '/companies/' + id ? id : ""

Is using an inline if statement, so what it's saying is, if id != null, false, or undefined, add id to the companies/ string and else simply add nothing.

Edit

Regarding js classes, you could do something like this:

// Seperate into a different file and export it
class Companies { 
    constructor (id) {
        this.id= id;
        this.body = "";
        // You can add more values for this particular object
        // Or you can dynamically create them without declaring here 
        // e.g company.new_value = "value"
    }

    get (cb) {
        http.get({
            host: 'http://api.com',
            path: '/companies/' + this.id ? this.id : ""
        }, function(response) {
            response.on('data',(d) => {
                this.body += d;
                cb (); // callback
            });
        }); 
    }

    post () {
        // You can add more methods ... E.g  a POST method.
    }
    put (cb) {
        http.put({
            host: 'http://api.com',
            path: '/companies/' + this.id ? this.id : "",
            ... Other object values here ...
        }, function(response) {
            response.on('data',(d) => {
                ... do something here with the response ...
                cb(); //callback 
            });
        }); 
    }
}

Your router class could then use this class like so:

router.get('/companies/:id?', function(req, res, next) {
    var id = req.params.id;
    var company = new Companies(id);
    company.get(() => {
        // We now have the response from our http.get
        // So lets access it now!
        // Or run company.put(function () {...})
        console.log (company.body);
        res.render('companies', {data: company.body});
    });

});

I've added callbacks here for simplicity but I recommend using promises: https://developers.google.com/web/fundamentals/getting-started/primers/promises

like image 29
James111 Avatar answered Oct 20 '22 10:10

James111


The general way to approach refactoring is to identify what's different in your code and extract that to be a dynamic part that is passed into a function that contains the common code.

For instance, the two things that are different here are the path on which the requests happen

'/companies' vs '/companies/:id'

And the related path that gets passed to http.get

'/companies' vs '/companies/' + req.params.id

You can extract these and pass them into a function that will assign a handler for you.

Here's a generic approach:

// props contains the route and 
// a function that extracts the path from the request
function setupGet(router, props) {
  router.get('/' + props.route, function(req, res, next) {

    http.get({
      host: 'http://api.com',
      path: props.getPath(req)
    }, function(response) {
      var body = '';
      response.on('data', function(d) {
        body += d;
      });
    });

    res.render('company', {
      data: body
    });
  });
}

And then call it with the two options:

setupGet(router, { 
  route: 'companies', 
  getPath: function(req) {
    return 'companies';
  }
});

setupGet(router, { 
  route: 'companies/:id', 
  getPath: function(req) {
    return 'companies' + req.params.id;
  }
});

The benefit here is that you can use any combination of routes and paths as well as use other req properties to determine the path.

Another thing you need to realize is that your res.render call will happen before you do body += d, because the former happens synchronously right after the call to http.get and the latter happens asynchronously (sometime later).

You probably want to put the render method in the callback itself.

// props contains the route and 
// a function that extracts the path from the request
function setupGet(router, props) {
  router.get('/' + props.route, function(req, res, next) {

    http.get({
      host: 'http://api.com',
      path: props.getPath(req)
    }, function(response) {
      var body = '';
      response.on('data', function(d) {
        body += d;

        // probably want to render here
        res.render('company', {
          data: body
        });
      });
    });
  });
}
like image 1
nem035 Avatar answered Oct 20 '22 09:10

nem035