Create models in expressjs

I have an express application 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 more maintainable, as you can see that I am 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 this?

+5
source share
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 cannot tell if http.get () will be and its callback completed when the request is completed using res.render. This means that res.render should always happen in a 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", since the model is a data set. A service is a collection of logical functions.

To create a company service module, follow these steps:

 // File companyService.js const request = require('request'); // This is just one of many ways to encapsulate logic in JavaScript (eg 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(/*...*/) }); 
+1
source

In this case, there is no need to have several routes! Can you use 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}); }); 

Code bit here:

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

Uses the inline if statement, so what it says is , if id != null, false, or undefined , add id to the companies/ line and else just don't add anything.

Edit

As for js classes, you can 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 // eg 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 ... Eg 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 }); }); } } 

Then your router class can use this class as follows:

 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 have added callbacks here for simplicity, but I recommend using promises: https://developers.google.com/web/fundamentals/getting-started/primers/promises

+2
source

The general way to approach refactoring is to determine what is different from your code, and extract it as the dynamic part that is passed to a function that contains the general code.

For example, two different things here are the way in which requests occur.

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

And the related path that is passed to http.get

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

You can extract them and pass them to a function that the handler will assign to you.

Here's a general 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 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 advantage 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 make body += d , because the former happens synchronously right after the http.get call, and the latter happens asynchronously (after some time).

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 }); }); }); }); } 
+1
source

Source: https://habr.com/ru/post/1258443/


All Articles