Forward any Nodejs request to another servers - javascript

Imagine I want to implement API Gateway for microservices. I have this type of route in gateway:
app.all('/api/users-service/*', (req, res, next) => {
});
What I want to do is forward given req to service without knowing it's GET, POST or something else. I also may need to build two different request, forward them to two different services and return merged response in gateway. something like that:
app.all('/api/users-service/*', (req, res, next) => {
const user = await request(req, 'http://first-service/api/user/' + req.body.userId);
const products = await request(req, 'http://second-service/api/products');
res.status(200).json({
user: user,
products: products
});
});
I know it's bad example but hope you will understand what I am trying to do.

You can definitely forward requests to other services, in some cases a proxy will be exactly what you need but in others, you may want to do something more sophisticated like process requests and responses.
In this case you could try something like below:
const rp = require("request-promise-native");
const bodyParser = require("body-parser");
const userServiceRootUrl = "http://first-service/api/user/";
const productServiceRootUrl = "http://second-service/api/products/";
app.all("/api/users-service/*", bodyParser.json(), async (req, res) => {
console.log("/api/users-service/ path:", req.params[0]);
const user = await rp({ baseUrl: userServiceRootUrl, url: req.params[0], body: req.body, json: true, method: req.method });
const products = await rp({ url: productServiceRootUrl });
res.status(200).json({
user: user,
products: products
});
});
In this example we're using request-promise-native as it gives us a promise based api, you could also use node-fetch or some other http client.
What you can also do in this case is create mock endpoints in your express server to test response handling, for example:
app.all("/api/user/*", bodyParser.json(), async (req, res) => {
console.log("/api/user/", req.path);
res.json( { name: "joe smith" })
});
app.all("/api/products/", bodyParser.json(), async (req, res) => {
console.log("/api/products/", req.path);
res.json( [{ id: 1, name: "some product" }]);
});
Then simply change your userServiceRootUrl and productServiceRootUrl as appropriate, e.g.
http://localhost:<port>/api/user/
http://localhost:<port>/api/products/

Related

Why I am getting Query was already executed error?

I have the following Schema and Model:
const userSchema = new mongoose.Schema({
name: String,
})
const UserModel = mongoose.model('User', userSchema, 'users')
and I have written the following express middleware, which simply takes one argument, awaits that argument, set the returned value from that awaiting job to the req object on a property called gottenDocs, (i.e.: req.gottenDocs)
function getDocumentsMw(query) {
return async (req, res, next) => {
const dbRes = await query
req.gottenDocs = dbRes
next()
}
}
and I have the following route:
app.get(
'/users',
getDocumentsMw(UserModel.find({})),
(req, res, next) => {
const gottenDoc = req.gottenDocs
res.status(200).json({
status: 'success',
data: gottenDoc,
})
})
That's all I have, now, when I request [ GET " /users " ] I recieve the following response which is great, and nothing is wrong:
{
"status": "success",
"data": []
}
but, the weird thing is when I request this route again, it throws this error:
MongooseError: Query was already executed: User.find({})
What could be the problem? is it a bug in Nodejs? which could be hmmm, something like, that it is not removing the function call from the call stack after the response has been sent?
any help appreciated.
The problem is on this line
getDocumentsMw(UserModel.find({})),
Here you create a query once the application start, because of that the query is created once but executed multiple times.
You may need to refactor your code to something like that
getDocumentsMw(() => UserModel.find({})),
Now you are passing a function and not a query. The function creates a query, aka factory. Next step is to refactor getDocumetnsMw to call the function to create a query when it needs to do something with it.
function getDocumentsMw(queryFactory) {
return async (req, res, next) => {
const dbRes = await queryFactory()
req.gottenDocs = dbRes
next()
}
}

How do I make a live search result in node.js and mongoDb

I am trying to implement a feature where I have an input on this route to make a live search of employees in the database
app.get('/delete' , isLoggedIn , (req , res) => {
res.render('pages/delete')
})
This route serves the search input. How do I create a live search based on a keyup event listener that sends the data to mongoDb/mongoose to search and return the results on the page?
I know how to do the event listener to get what is typed like so which is in the delete.js file
const deleteSearchInput = document.querySelector('#search-input');
deleteSearchInput.addEventListener('keyup' , (e) => {
let search = e.target.value.trim()
})
How do I send the value "e" to a post route to do the search and return it to the page
AJAX (using the JavaScript fetch API). AJAX allows JavaScript to send requests to the server without reloading.
const deleteSearchInput = document.querySelector('#search-input');
deleteSearchInput.addEventListener('keyup' , (e) => {
let search = e.target.value.trim();
fetch('/delete', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({search})
}).then(res =>
res.json()
).then(data => {
console.log(data.result); // <-- success!
}).catch(err => {
alert('error!');
console.error(err);
});
});
Then you have changes to make to the server side. Since you're sending a POST request, you need to create a handler to POST:
app.post('/delete', isLoggedIn, (req, res) => {
res.send('success!');
});
This will handle post requests, and only post requests. Now to get the value of whatever you sent to the server, we need to use an npm package called body-parser, which parses the incoming request. Run the following command in shell:
npm i body-parser
Then at the top of your server file before declaring your routes import and use the body-parser library:
const bodyParser = require('body-parser');
app.use(bodyParser.json()); // <-- add the JSON parser
Finally change your handler again:
app.post('/delete', isLoggedIn, (req, res) => {
const { search } = req.body;
console.log(search);
// ... do whatever you want and send a response, e.g.:
const result = 'my awesome message';
res.json({ result });
});
And that's how you do it.

Whats the best way save a users last route location using express.js

Consider a simple serverside node app.
const router = express.Router();
router.get("/path1",function(req,res) {
...
});
router.get("/path2",function(req,res) {
...
});
What's a great way to save the user's last location using localStorage or Cookies or anything? i.e.) www.somewebpage.com/path1 or www.somewebpage.com/path2
I was hoping for something like
router. <forAllPaths>(o => {
localStorage.setItem(<...set url code>);
})
I would recommend that you use redis and in each route you generate a middleware that stores or updates the last route used
const redis = require("redis");
const client = redis.createClient();
const middleware = async (res, req, next) => {
client.set(userId, req.originalUrl);
next()
}
client.on("error", error => {
console.error(error);
});
const router = express.Router();
router.get("/path1", middleware, async (req,res) => {
});
router.get("/path2", middleware, async (req,res) => {
});

External API Calls With Express, Node.JS and Require Module

I have a route as followed:
var express = require('express');
var router = express.Router();
var request = require('request');
router.get('/', function(req, res, next) {
request({
uri: 'http://www.giantbomb.com/api/search',
qs: {
api_key: '123456',
query: 'World of Warcraft: Legion'
},
function(error, response, body) {
if (!error && response.statusCode === 200) {
console.log(body)
}
}
});
});
module.exports = router;
I'm trying to make an API call to the Giant Bomb API to bring back whatever data it has about World of Warcraft.
The problem is, the route just loads; it doesn't do anything or it doesn't time out, it's just continuous loading.
I don't know what I'm doing wrong, but that being said... I don't know what's right either. I'm trying to learn as I go along.
Any help would be great.
Thanks
You need to take the data you get from request() and send it back as the response to the original web server request. It was just continuously loading because you never sent any sort of response to the original request, thus the browser was just sitting there waiting for a response to come back and eventually, it will time out.
Since request() supports streams, you can send back the data as the response very simply using .pipe() like this.
var express = require('express');
var router = express.Router();
var request = require('request');
router.get('/', function(req, res, next) {
request({
uri: 'http://www.giantbomb.com/api/search',
qs: {
api_key: '123456',
query: 'World of Warcraft: Legion'
}
}).pipe(res);
});
module.exports = router;
This will .pipe() the request() result into the res object and it will become the response to the original http request.
Related answer here: How to proxy request back as response
Edit in 2021. The request() library has now been deprecated and is no longer recommended for new code. There are many alternatives to choose from. My favorite is the got() library. The above could be accomplished using it like this. This also upgrades to use the pipeline() function which is a better version of .pipe() with more complete error handling.
const router = require('express').Router();
const got = require('got');
const { pipeline } = require('stream');
router.get('/', function(req, res) {
const dataStream = got.stream({
uri: 'http://www.giantbomb.com/api/search',
qs: {
api_key: '123456',
query: 'World of Warcraft: Legion'
}
});
pipeline(dataStream, res, (err) => {
if (err) {
console.log(err);
res.sendStatus(500);
}
});
});
module.exports = router;
For Laravel users,
First of all install npm i axios package if not.
var axios = require('axios');
var config = {
/* Your settings here like Accept / Headers etc. */
}
axios.get('http://local.dev/api/v1/users', config)
.then(function(response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
Hopefully it will help someone!
Per every route in Express, it is necessary to send a response (partial or complete) or call next, or do both. Your route handler does neither. Try
var express = require('express');
var router = express.Router();
var request = require('request');
router.get('/', function(req, res, next) {
request({
uri: 'http://www.giantbomb.com/api/search',
qs: {
api_key: '123456',
query: 'World of Warcraft: Legion'
},
function(error, response, body) {
if (!error && response.statusCode === 200) {
console.log(body);
res.json(body);
} else {
res.json(error);
}
}
});
});
module.exports = router;
and see what data this route handler responds with.
In 2022
In node
const fetch = (...args) => import('node-fetch')
.then(({default: fetch}) => fetch(...args));
app.get('/checkDobleAPI', async (req, res) => {
try {
const apiResponse = await fetch(
'https://jsonplaceholder.typicode.com/posts')
const apiResponseJson = await apiResponse.json()
console.log(apiResponseJson)
res.send('Running 🏃')
} catch (err) {
console.log(err)
res.status(500).send('Something went wrong')
}
})

Calling Express Route internally from inside NodeJS

I have an ExpressJS routing for my API and I want to call it from within NodeJS
var api = require('./routes/api')
app.use('/api', api);
and inside my ./routes/api.js file
var express = require('express');
var router = express.Router();
router.use('/update', require('./update'));
module.exports = router;
so if I want to call /api/update/something/:withParam from my front end its all find, but I need to call this from within another aspect of my NodeJS script without having to redefine the whole function again in 2nd location
I have tried using the HTTP module from inside but I just get a "ECONNREFUSED" error
http.get('/api/update/something/:withParam', function(res) {
console.log("Got response: " + res.statusCode);
res.resume();
}).on('error', function(e) {
console.log("Got error: " + e.message);
});
I understand the idea behind Express is to create routes, but how do I internally call them
The 'usual' or 'correct' way to handle this would be to have the function you want to call broken out by itself, detached from any route definitions. Perhaps in its own module, but not necessarily. Then just call it wherever you need it. Like so:
function updateSomething(thing) {
return myDb.save(thing);
}
// elsewhere:
router.put('/api/update/something/:withParam', function(req, res) {
updateSomething(req.params.withParam)
.then(function() { res.send(200, 'ok'); });
});
// another place:
function someOtherFunction() {
// other code...
updateSomething(...);
// ..
}
This is an easy way to do an internal redirect in Express 4:
The function that magic can do is: app._router.handle()
Testing: We make a request to home "/" and redirect it to otherPath "/other/path"
var app = express()
function otherPath(req, res, next) {
return res.send('ok')
}
function home(req, res, next) {
req.url = '/other/path'
/* Uncomment the next line if you want to change the method */
// req.method = 'POST'
return app._router.handle(req, res, next)
}
app.get('/other/path', otherPath)
app.get('/', home)
I've made a dedicated middleware for this : uest.
Available within req it allows you to req.uest another route (from a given route).
It forwards original cookies to subsequent requests, and keeps req.session in sync across requests, for ex:
app.post('/login', async (req, res, next) => {
const {username, password} = req.body
const {body: session} = await req.uest({
method: 'POST',
url: '/api/sessions',
body: {username, password}
}).catch(next)
console.log(`Welcome back ${session.user.firstname}!`
res.redirect('/profile')
})
It supports Promise, await and error-first callback.
See the README for more details
Separate your app and server files with the app being imported into the server file.
In the place you want to call your app internally, you can import you app as well as 'request' from 'supertest'. Then you can write
request(app).post('/someroute').send({
id: 'ecf8d501-5abe-46a9-984e-e081ac925def',
etc....
});`
This is another way.
const app = require('express')()
const axios = require('axios')
const log = console.log
const PORT = 3000
const URL = 'http://localhost:' + PORT
const apiPath = (path) => URL + path
app.get('/a', (req, res) => {
res.json('yoy')
})
app.get('/b', async (req, res) => {
let a = await axios.get(apiPath('/a'))
res.json(a.data)
})
app.listen(PORT)

Categories