I'm new to node/express. I have a simple express app with one post route ('/'). Given a list of GitHub users names, it should return information about those developers.
For example, if I post
{ "developers": ["JohnSmith", "JaneDoe"] }
It should return
[
{
"bio": "Software Engineer at Google",
"name": "John Smith"
},
{
"bio": "Product Manager, Microsoft",
"name": "Jane Doe"
}
]
This is what I have so far
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
app.post('/', function(req, res, next) {
try {
let results = req.body.developers.map(async d => {
return await axios.get(`https://api.github.com/users/${d}`);
});
let out = results.map(r => ({ name: r.data.name, bio: r.data.bio }));
return res.send(out);
} catch {
next(err);
}
});
app.use((err, req, res, next) => {
res.send("There was an error");
})
app.listen(3000, function(){
console.log("Server started on port 3000!");
});
When I try this in a tool like Insomnia, I keep getting There was an error.
You can use res.formate() as per this docs...
Or set type for res as per this Docs...
res.type('json')
From middleware, you are always saying res.json("there is some error"), why are you keeping it there if not needed. if you remove app.use(). you will see the request is returning you names of the developer
read this for error handling.
https://expressjs.com/en/guide/error-handling.html
The way you return JSON in an Express API is as follows:
res.status(200).json(out)
See https://expressjs.com/it/api.html for the full documentation.
You're getting There was an error. Because, there is an error in your / route. But, the thing is you haven't wrote the err parameter with catch. If add that one in your catch (of your try/catch block) and console.log it, you'll see the error
TypeError: Cannot read property 'name' of undefined
You're getting this error because, results variable is actually an array of pending Promise. You need to first resolve these pending promises. One way to do is,
app.post("/", function (req, res, next) {
try {
const developers = req.body.developers;
let promises = [];
developers.map((d) => {
promises.push(axios.get(`https://api.github.com/users/${d}`));
});
Promise.all(promises).then((responses) => {
let out = responses.map((r) => ({
name: r.data.name,
bio: r.data.bio,
}));
return res.send(out);
});
} catch (err) {
next(err);
}
});
Related
const express = require("express");
const expressAsyncHandler = require("express-async-handler");
const app = express();
const f = async () => {
return false;
};
app.get(
"/",
expressAsyncHandler(async () => {
throw await f();
}),
() => {
console.log("the bug!");
}
);
app.use((err, req, res, next) => {
console.log("caught!", err);
});
app.listen(4000, () => console.log("listening on port 4000..."));
Expected output on the console:
"caught!".
output:
the bug!.
question: Why? Is it a bug in async-express-handler package or is it a normal JavaScript behaviour? what if I want to throw await something inside? how ?
Your problem is that you're throwing the value false. This doesn't fit into nodejs' callback conventions (and by extension, express error handling), which requires the err parameter to get a truthy value to be considered an error. A much simpler way to reproduce the issue:
app.get(
"/",
(req, res, next) => {
Promise.reject(false).catch(next);
// or even just:
next(false);
},
() => {
console.log("the bug!");
}
);
So just don't do that! Always throw an Error, not a string, not something else, and this principle also holds for promise rejections.
I am creating an express app using mongoose with the intention of connecting this to React for the frontend.
I have listed some CRUD operations for a customer controller below but there are a few things I do not like about this approach.
When using Customer.findById with a valid ObjectID that is not found, it returns null with a 200 response code. I want this to return 404 if no customer was found. I realise I could change the catch response to a 404, but I want to have some generic error handling incase the server goes down during the request or an invalid ObjectId was provided, which brings me to my next item.
If I provide an invalid ObjectId I want to provide some meaningful message, is 500 the right response code?
Error handling: Am I returning errors the correct way? currently errors return a string with the error message. Should I return JSON instead? e.g. res.status(500).json({error: error.message). I am planning on connecting this to react (which I am still learning) and I assume the UI will need to display these messages to the user?
findById is repeated in getCustomerById, updateCustomer, and deleteCustomer. I feel this is bad practice and there must be a more streamlined approach?
I want to have one function that validates if the ObjectId is valid. I am aware that I can do this is the routes using router.params but I'm not sure if checking for a valid id should be in the routes file as it seems like something the controller should be handling? See routes example below from another project I did.
What are the best practices and suggested ways to improve my code, based on the above?
I have read the documentation from mongoose, mozilla, and stackoverflow Q&A but they don't seem to address these issues (at least I could not find it).
I am really after some guidance or validation that what I am doing is correct or wrong.
customer.controller.js
const Customer = require("../models/customer.model");
exports.getCustomers = async (req, res) => {
try {
const customers = await Customer.find();
res.status(200).json(customers);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.getCustomerById = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.addCustomer = async (req, res) => {
try {
const customer = new Customer(req.body);
await customer.save().then(res.status(201).json(customer));
} catch (error) {
res.status(500).send(error.message);
}
};
exports.updateCustomer = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
Object.assign(customer, req.body);
customer.save();
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
exports.deleteCustomer = async (req, res) => {
try {
const customer = await Customer.findById(req.params.id);
await customer.remove();
res.status(200).json(customer);
} catch (error) {
res.status(500).send(error.message);
}
};
Router.params example
This is a routes file (not related to my current app) and is provided as an example of how I have used router.params in the past.
const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const Artist = require("../models/Artist");
const loginRequired = require("../middleware/loginRequired");
const {
getArtists,
addArtist,
getArtistById,
updateArtist,
deleteArtist,
} = require("../controllers/artistController");
router
.route("/")
.get(loginRequired, getArtists) // Get all artists
.post(loginRequired, addArtist); // Create a new artist
router
.route("/:id")
.get(loginRequired, getArtistById) // Get an artist by their id
.put(loginRequired, updateArtist) // Update an artist by their id
.delete(loginRequired, deleteArtist); // Delete an artist by their id
router.param("id", async (req, res, next, id) => {
// Check if the id is a valid Object Id
if (mongoose.isValidObjectId(id)) {
// Check to see if artist with valid id exists
const artist = await Artist.findOne({ _id: id });
if (!artist) res.status(400).json({ errors: "Artist not found" });
res.locals.artist = artist;
res.locals.artistId = id;
next();
} else {
res.status(400).json({ errors: "not a valid object Id" });
}
});
module.exports = router;
i personly like to make error handeling more global so i would write something like
constPrettyError = require('pretty-error')
const pe = new PrettyError()
const errorHandler = (err, req, res, next) => {
if (process.env.NODE_ENV !== 'test') {
console.log(pe.render(err))
}
return res
.status(err.status || 500)
.json({ error: { message: err.message || 'oops something went wrong' } })
}
module.exports = errorHandler
as a handler
the in your index / server file
app.use(errorHandler)
then in your handlers just
} catch (err) {
next(err);
}
as an example
if (!artist) next({ message: "Artist not found" ,status:404 });
also, note that you can customize this error handler to switch case (or object) a custom error per status as well if you want
const errorHandler = (err, req, res, next) => {
if (process.env.NODE_ENV !== 'test') {
console.log(pe.render(err))
}
const messagePerStatus = {
404: 'not found',
401: 'no authorization'
}
const message = messagePerStatus[err.status]
return res
.status(err.status || 500)
.json({
error: { message: message || err.message || 'oops something went wrong' }
})
}
then just
if (!artist) next({status:404 });
I also agree with answer by Asaf Strilitz but still need to show what i do in my projects
Create a custom error class
AppError.js
class AppError extends Error {
constructor(statusCode, message) {
super();
// super(message);
this.statusCode = statusCode || 500 ;
this.message = message || "Error Something went wrong";
}
}
module.exports = AppError;
Create an error handling middleware
errors.js
const AppError = require("../helpers/appError");
const errors = (err, req, res, next) => {
// console.log(err);
let error = { ...err };
error.statusCode = error.statusCode;
error.message = error.message;
res.status(error.statusCode).json({
statusCode: err.statusCode,
message: err.message,
});
};
exports.errors = errors;
Create a middleware to validate object id
validateObjectId.js
const mongoose = require("mongoose");
const AppError = require("appError");
module.exports = function (req, res, next) {
const { _id } = req.params;
if (_id && !mongoose.Types.ObjectId.isValid(_id)) {
throw new AppError(422, "Invalid ID field in params");
}
next();
};
In your app.js
const { errors } = require("errors");
// At the end of all middlewares
// Error Handler Middleware
app.use(errors);
In your routes file
const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const Artist = require("../models/Artist");
const loginRequired = require("../middleware/loginRequired");
const validateId = require("validateObjectId");
const {
getArtists,
addArtist,
getArtistById,
updateArtist,
deleteArtist,
} = require("../controllers/artistController");
// Your routes
router
.route("/:id")
.get(validateId, loginRequired, getArtistById) // Get an artist by their id
.put(validateId, loginRequired, updateArtist) // Update an artist by their id
.delete(validateId, loginRequired, deleteArtist); // Delete an artist by their id
module.exports = router;
Now regarding findById method being repeated i dont see anything bad in that as it is specific to database call still you can introduce a staic method on model itself or create a single method on cntroller but still need to check if it returns the found object or not and handle the error on that.
I was trying to make a routes for each ID I using a forEach loop but It stay loading until timeout reaches, all expected values are in place, all good but the second route is not running, I was fighting it despretly until now. I made sure there is a problem.
server.js
const router = require('express').Router();
function isAuthorized(req, res, next) {
if (req.user) {
next();
}
else {
res.redirect('/login')
}
}
let myguild = [];
router.get(`*`, isAuthorized, (req, res) => {
res.status(200);
console.log("wow");
console.log(req.user.guilds.length)
req.user.guilds.forEach(guild => {
myguild.push(guild);
})
console.log("Finished");
myguild.forEach(guild => {
console.log('Started')
router.get(guild.id, (req, res) => { // here is the problem
console.log("uh")
res.send("HAMBURGER")
console.log(req, res, guild)
})
console.log("Outed")
})
});
module.exports = router;
output:
wow
23
Finished
Started
Outed
Started
Outed
Started
Outed
Star... 'there is more but this is enough'
It should behave and run within server/${guild.id} but got (failed) request
Any Ideas?
You might need to redesign the API to better fit what you're trying to accomplish. If you already know which guilds are available then you'd need to create those before the server is initialized.
Even if they come from a database or are dynamic, you can loop through the guild "options" and create endpoints then provide access to them only if the user is qualified.
const { guilds } = require('./config')
const guildHandler = (req, res) => {
// Assuming you're doing more here
res.send('Hamburger')
}
guilds.forEach(guild => router.get(`/guilds/${guildId}`, guildHandler)
Or if you are NOT doingg something different in the middleware for each guild then you could just have a single route for guild.
router.get('/guilds/:guildId, guildHandler)
Not really sure what you're trying to accomplish but checkout out the Express docs. They solve most use cases fairly easily.
https://expressjs.com/en/api.html#req
You never call res.end() from your outer res.get() handler, so the request never completes.
And, with respect, creating route handlers like that in a loop is a mistake. It will lead to real performance trouble when your app gets thousands of guilds.
You'll want to use just one route, with a named route parameter, something like this.
const createError = require('http-errors')
router.get(':guildid', isAuthorized, (req, res, next) => {
const guildid = req.params.guildid
if (req.user.guilds.includes(guild)) {
console.log("uh")
res.send("HAMBURGER").end()
console.log(req, res, guildid)
} else {
next(createError(404, guildId + ' not found'))
}
})
Thanks for everyone helped.
Inspired answer
Final Result:
server.js
router.get('/:guildid', isAuthorized, (req, res, next) => {
console.log('started')
const guildid = req.params.guildid
if (req.user.guilds.some(guild => guild.id === guildid)) {
console.log('uh')
res.send("HAMBURGER").end()
} else {
res.sendStatus(404);
}
})
I'm trying to send two json but It doesn't work. It prints TypeError: res.json is not a function but I don't get why It happens. Is there any ideas? Thank you !!
app.post('/danger', function response(req, res) {
let placeId = req.body.data;
let option = {
uri: 'https://maps.googleapis.com/maps/api/directions/json?',
qs: {
origin:`place_id:${placeId[0]}`, destination: `place_id:${placeId[1]}`,
language: 'en', mode: 'walking', alternatives: true, key: APIKey
}
};
rp(option)
.then(function(res) {
let dangerRate = dangerTest(JSON.parse(res), riskGrid);
res.json({ data: [res, dangerRate]});
})
.catch(function(err) {
console.error("Failed to get JSON from Google API", err);
})
});
Because you're overwriting your res variable in the .then of your rp function:
app.post('/danger', function response(req, res) { //see, "res" here was being overwritten
..
..
rp(option).then(function(response) { //change the variable name of "res" to "response" (or "turtles", who cares, just dont overwrite your up most "res")
I got this error message because I had the arguments in the wrong order in the handler method. (amateur's fault)
Wrong order: (res, req)
app.get('/json', (res, req) => {
res.json({
"message": "Hello json"
});
});
Right order: (req, res)
app.get('/json', (req, res) => {
res.json({
"message": "Hello json"
});
});
.json isn't a function. Unless you are using a library that makes it one, JavaScript uses JSON (with two methods .parse() and .stringify() one of which you use in the line above).
If you are trying to set an object property by the name of .json then it would be:
res.json = {data: [res, dangerRate]};
With new httpClient libary, you don't need to call .json() method, Just use this simple map instead of the json method.
.map(res => res );
check the sequence=>
If you have write nested res and you write (res,req) you get error.
order plays a very big role in app.get('/',(req,res)=>{ });
you can change the name instead of req and res but the second arg should be of res and the first should be of req
like app.get('/',(a,b)=>{ b.json({});
above syntax is res.json({}) ,we are sending a json file at '/'
const express = require('express');
const path = require("path");
const bodyParser = require('body-parser');
const app = express();
const PORT = 80;
app.use(bodyParser.urlencoded({
extended: false
}));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname + "/index.html"));
});
app.post('/api/v1', (req, res) => {
// const userName=req.body.name;
res.send("<h1>done</h1>");
console.log(req.body);
});
app.get("/api/v1/userdata", (req, res) => {
res.json({
name: "your_Name",
email: "your_Email",
password: "hexed",
});
});
app.listen(PORT, () => {
console.log("listening on port 80");
});
I want to slightly change default expressjs behaviour of res.json(obj) method. I am trying to override it in my own middleware, the thing is I need to call its original inside.
But now it just calls itself causing a stack overflow.
app.use(function(req, res, next) {
res.json = function(obj) {
function delete_null_properties(obj) {
// ...
}
delete_null_properties(obj);
res.json(obj);
};
next();
});
I don't know the inner workings of express very well, but it seems something like this should work
app.use(function(req, res, next) {
var json = res.json;
res.json = function(obj) {
function delete_null_properties(obj) {
// ...
}
delete_null_properties(obj);
json.call(this, obj);
};
next();
});
edit: changed json(obj) to json.call(this, obj) as per comment by user3537411 and this previous answer to a similar question
P.S. I started the answer with I don't know the inner workings of express very well to avoid the sort of comments that just put crap on an answer without really going into WHY an answer is bad ... instead I get the sort of comment that's equally pointless. You can't win with SO trolls
You can do like below, after const app = express(); dont use this.json and dont use this.send inside the function otherwise you will get a maximum call size error :
app.response.json = function(body: any) {
this.contentType('json').end(JSON.stringify(
{
code: ApiCode.OPERATION_SUCCES,
message: CommonMessages.OperationSuccess.message,
data: body,
description: CommonMessages.OperationSuccess.description
}
));
return this;
}
It also might be useful
https://github.com/muratcorlu/connect-api-mocker/pull/30
Mounting twice will apply only last one.
const express = require('../../node_modules/express');
const app = express();
// default response
app.use('/', (req, res, next) => {
next();
try {
res.send({
profile: {
first_name: 'Aaron',
last_name: 'Pol'
}
});
} catch (e) {
//
}
});
// definite state, where default response can be changed
app.use('/', (req, res) => {
res.send({
profile: {
first_name: 'John',
last_name: 'Pol'
}
});
});
app.listen(9090);