How to validate array of objects using Joi? - node.js

I am getting an array of objects to backend, where each object contains a service name.
The structure looks like below
[{"serviceName":"service1"},
{"serviceName":"service2"},..]
when I get the array at backend, I want to validate that every object in the array has serviceName property.
I had written the following code, but even though I pass valid array, I am getting validation error.
var Joi = require('joi');
var service = Joi.object().keys({
serviceName: Joi.string().required()
});
var services = Joi.array().ordered(service);
var test = Joi.validate([{serviceName:'service1'},{serviceName:'service2'}],services)
For the above code, I am always getting the validation error with message
"value" at position 1 fails because array must contain at most 1 items

replacing ordered with items will work.
let Joi = require('joi')
let service = Joi.object().keys({
serviceName: Joi.string().required(),
})
let services = Joi.array().items(service)
let test = Joi.validate(
[{ serviceName: 'service1' }, { serviceName: 'service2' }],
services,
)
For reference click here

A basic/ clearer example is as follows.
To validate a JSON request like this:
{
"data": [
{
"keyword":"test",
"country_code":"de",
"language":"de",
"depth":1
}
]
}
Here is the Joi validation:
seoPostBody: {
body: {
data: Joi.array()
.items({
keyword: Joi.string()
.required(),
country_code: Joi.string()
.required(),
language: Joi.string()
.required(),
depth: Joi.number()
.required(),
}),
},
};
This is what I am doing in NodeJs, might need some slight changes for other platforms

const test = {
body: Joi.array()
.items({
x: Joi.string().required(),
y: Joi.string().required(),
z: Joi.string().required(),
date: Joi.string().required(),
})
};

Just want to make it more clear. I'm currently using "#hapi/joi:16.1.7".
Let's say you want your schema to validate this array of objects.
const example = [
{
"foo": "bar",
"num": 1,
"is_active": true,
}
];
Then schema's rules should be:
var validator = require('#hapi/joi');
const rules = validator.array().items(
validator.object(
foo: validator.string().required(),
num: validator.number().required(),
is_active: validator.boolean().required(),
),
);
const { error } = rules.validate(example);

For Joi you can use below which is working fine for me, this will validate that array must have at-least on object with key serviceName-
const Joi = require('joi');
const itemsArray = Joi.array().items(
Joi.object({
serviceName: Joi.string().required(),
})
).min(1).required();
const itemSchema = Joi.array().items(itemsArray).when('checkout_type', {
is: 'guest',
then: Joi.array().required(),
}).required();
let schema = Joi.object().keys({
items: Joi.alternatives().try(itemsArray, itemSchema).required(),
});

Libraries like these are great but wouldn’t it be even better if we could use them in a more seamless way, like in a Request pipeline? Let’s have a look firstly how we would use Joi in an Express app in Node.js:
const Joi = require('joi');
app.post('/blog', async (req, res, next) => {
const { body } = req; const
blogSchema = Joi.object().keys({
title: Joi.string().required
description: Joi.string().required(),
authorId: Joi.number().required()
});
const result = Joi.validate(body, blogShema);
const { value, error } = result;
const valid = error == null;
if (!valid) {
res.status(422).json({
message: 'Invalid request',
data: body
})
} else {
const createdPost = await api.createPost(data);
res.json({ message: 'Resource created', data: createdPost })
}
});
The above works. But we have to, for each route:
create a schema
call validate()
It’s, for lack of better word, lacking in elegance. We want something slick looking
Building a middleware
Let’s see if we can’t rebuild it a bit to a middleware. Middlewares in Express is simply something we can stick into the request pipeline whenever we need it. In our case we would want to try and verify our request and early on determine whether it is worth proceeding with it or abort it.
So let’s look at a middleware. It’s just a function right:
const handler = (req, res, next) = { // handle our request }
const middleware = (req, res, next) => { // to be defined }
app.post( '/blog', middleware, handler )
It would be neat if we could provide a schema to our middleware so all we had to do in the middleware function was something like this:
(req, res, next) => {
const result = Joi.validate(schema, data)
}
We could create a module with a factory function and module for all our schemas. Let’s have a look at our factory function module first:
const Joi = require('joi');
const middleware = (schema, property) => {
return (req, res, next) => {
const { error } = Joi.validate(req.body, schema);
const valid = error == null;
if (valid) {
next();
} else {
const { details } = error;
const message = details.map(i => i.message).join(',');
console.log("error", message);
res.status(422).json({ error: message }) }
}
}
module.exports = middleware;
Let’s thereafter create a module for all our schemas, like so:
// schemas.js
const Joi = require('joi')
const schemas = {
blogPOST: Joi.object().keys({
title: Joi.string().required
description: Joi.string().required()
})
// define all the other schemas below
};
module.exports = schemas;
Ok then, let’s head back to our application file:
// app.js
const express = require('express')
const cors = require('cors');
const app = express()
const port = 3000
const schemas = require('./schemas');
const middleware = require('./middleware');
var bodyParser = require("body-parser");
app.use(cors());
app.use(bodyParser.json());
app.get('/', (req, res) => res.send('Hello World!'))
app.post('/blog', middleware(schemas.blogPOST) , (req, res) => {
console.log('/update');
res.json(req.body);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

Related

POST going directly to the catch error and not saving data using mongoose, MongoDB, NodeJS, and Express

I already tried some possible solutions and even created and wrote the code again but I am still getting errors. I have created a diminute version of my whole code which connects to the database using Mongoose but after the Schema is created and I import the model in places-controllers my data that I write in POSTMAN goes directly to:
FYI: In this case I want POST request from createPlace to properly work.
Data entry: URL: http://localhost:5000/api/places/
{
"title": "Punta Arena Stfdsfdsfsdfop",
"description": "One stop Stop. Does not have tr12affic lights.",
"busrespect": "12ysdfdsfsfes",
"address": "Avenida Solunna",
"creator": "52peru soiflsdjf36"
}
OUTPUT:
{
"status": "error caught"
}
which is what I told the program to define if the try did not work.
IN app.js I have the following code:
const express= require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const placesRoutes = require("./routes/places-routes");
const HttpError = require ("./models/http-error");
const app = express();
app.use(bodyParser.json());
app.use('/api/places', placesRoutes);
app.use((req, res, next) => {
const error= new HttpError('Route not available. Try something different?', 404);
throw error;
});
app.use((error, req, res, next) =>{
if (res.headerSent) {
return next(error);
}
res.status(error.code || 500)
res.json({message: error.message || "An unknown error occured! Sorry" });
});
url = '<mongo_url>'
mongoose.connect(url, {useNewUrlParser: true}).then(()=>{
console.log("Connected to database")
app.listen(5000);
}).catch(erro => {
console.log(erro)
});
In places-routes.js I have the following code:
const express = require('express');
const {check} = require('express-validator')
const placesControllers=require('../controllers/places-controllers');
const router = express.Router();
router.get('/:pid', placesControllers.getPlaceById );
router.get('/user/:uid',placesControllers.getPlacesByCreatorId );
router.post('/' ,[
check('title')
.not()
.isEmpty(),
check('description').isLength({ min: 5 }),
check('address')
.not()
.isEmpty()
],
placesControllers.createPlace);
router.patch('/:pid', [
check('title')
.not()
.isEmpty(),
check('description').isLength({ min: 5 })
] , placesControllers.updatePlace );
router.delete('/:pid', placesControllers.deletePlace);
module.exports=router;
In places-controllers.js I have the following code:
const HttpError = require('../models/http-error');
const { validationResult } = require('express-validator');
//const getCoordsForAddress= require('../util/location');
const BusStop = require('../models/place');
let INITIAL_DATA = [
{
id: "p1",
title: "Samoa Stop",
description: "My first bus stop in Lima",
//location: {
// lat: 40.1382,
// lng:-23.23
// },
address: "Av. La Molina interseccion con calle Samoa",
busrespect: "yes",
creator: "u1"
}
];
const getPlaceById = (req, res, next) => {
const placeId = req.params.pid // Accessing the p1 in pid URL scrapping {pid:'p1'}
const place= INITIAL_DATA.find(p => { //find method goes over each element in the array, the argument p represents the element where find loop is
return p.id ===placeId
});
if (!place) {
const error= new HttpError('No bus stop found for the provided ID.', 404);
throw error;
}
res.json({place: place});
};
const getPlacesByCreatorId = (req, res, next)=> {
const userId = req.params.uid;
const places = INITIAL_DATA.filter(p=>{ //filter to retrieve multiple places, not only the first one
return p.creator ===userId;
});
if (!places || places.length===0) {
return next(
new HttpError('Could not find bus stops for the provide user id', 404)
);
}
res.json({places});
};
const createPlace = async (req, res,next) => {
const errors = validationResult(req);
if (!errors.isEmpty()){
return next(new HttpError ('Invalid bus stop please check your data', 422));
}
//const { title, description, busrespect, address, creator } = req.body; //erased location for now.
/* let place = new BusStop({
title: req.body.title,
description: req.body.description,
busrespect: req.body.busrespect,
address : req.body.address,
creator: req.body.creator
})
awaitplace.save()
.then(response=>{
res.json({
message : "Employee added sucessfully!"
})
})
.catch(err=>{
res.json({
message : "An error has occured!"
})
})
} */
const { title, description, busrespect, address, creator } = req.body;
try {
await BusStop.create({
title:title,
description: description,
busrespect:busrespect,
address: address,
creator: creator
});
res.send({status: "ok"});
} catch(error) {
res.send({status:"error caught"});
}
};
const updatePlace = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()){
console.log(errors);
throw new HttpError ("Invalid inputs passed, please check your data ", 422);
};
const { title, description } = req.body;
const placeId = req.params.pid;
const updatedPlace = { ...INITIAL_DATA.find(p => p.id === placeId)};
const placeIndex = INITIAL_DATA.findIndex(p => p.id === placeId);
updatedPlace.title = title;
updatedPlace.description = description;
INITIAL_DATA[placeIndex] = updatedPlace;
res.status(200).json({place: updatedPlace});
};
const deletePlace = (req, res, next) => {
const placeId = req.params.pid;
if (!INITIAL_DATA.find(p=> p.id ===placesId))
throw new HttpError('Could not find a bus stop for that ID ')
INITIAL_DATA = INITIAL_DATA.filter(p=> p.id !== placeId)
res.status(200).json({message: 'Deleted Place'});
};
exports.getPlaceById= getPlaceById;
exports.getPlacesByCreatorId = getPlacesByCreatorId;
exports.createPlace = createPlace;
exports.updatePlace = updatePlace;
exports.deletePlace = deletePlace;
Inside models folder I have two files: http-error.js which has this code:
class HttpError extends Error {
constructor(message, errorCode) {
super (message);
this.code = errorCode;
}
}
module.exports = HttpError;
The other file inside is the schema which is place.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const placeSchema = new Schema({
title: {
type: String
},
description: {
type: String
},
address: {
type: String
},
busrespect: {
type: String
},
creator: {
type: String
}
},
)
const BusStop = mongoose.model('BusStop', placeSchema)
module.exports= BusStop
Summary: somewhere in the try catch part from createPlace something is going wrong since my data entry is always going to the error status I indicated in that part.

how do I get req.query nextjs api?

I have this API, and I am trying to get the query I pass in the URL (such as products?page=1&limit=10) but I keep getting an empty object
const handler = nc().use(Cors());
handler.get(async (req, res) => {
await db.connect();
console.log(req.query)
const products = await Product.paginate({}, { page: 1, limit: 30 });
res.send(products);
});
export default handler;
console.log(req.query.page, req.query.limit)
You can pass any props to your api
const desiredId = 'xyz'
'https://yourAPI/getSomething?any_name_you_want='+ desiredId
//in your api
console.log(req.query.any_name_you_want)
Well, I have somehow managed to do it even though it probably isn't the best way. It works though and my main focus is not the backend, so I guess it's good enough
In redux toolkit query:
endpoints: (builder) => ({
getAllProducts: builder.query({
query: (page = 1) => `${baseUrl}?page=${page}`,
}),
In my index.js
const router = useRouter();
const [page, setPage] = useState(1);
const { data, isLoading, error } = useGetAllProductsQuery(page);
const handlePaginationChange = (e, value) => {
e.preventDefault();
setPage(value);
router.push(`/products?page=${value}`);
};
and my API route:
const handler = nc().use(Cors());
handler.get(async (req, res) => {
await db.connect();
const products = await Product.paginate({}, { page: req.query.page, limit: 9 });
res.send(products);
});

How do I reference documents from other collection in express

I have 2 collections here >>course & author
I need to prepare my course schema in such a way that it references the author and at the post request I only need to put the id.I am using #joi13 This is what I have done so far.
course schema
const mongoose = require('mongoose')
const Joi = require('joi')
Joi.objectId= require('joi-objectid')(Joi)
// schema
const courseSchema = new mongoose.Schema({
...
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
}
})
// the model
const Course = mongoose.model('Courses', courseSchema)
// validation
const courseValidation = (course) => {
const schema = {
...
authorId: Joi.objectId().required()
}
return Joi.validate(course, schema)
}
the course router
const {Course, validate} = require('../models/course')
const express = require('express')
const router = express.Router()
const {Author} = require('../models/author')
// post
router.post('/', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send(error.details[0].message)
const title = await Course.findOne({title : req.body.title})
if (title) return res.status(400).send('That user exists')
const author = await Author.find()
if (!author) return res.status(404).send('No such Author')
let course = new Course({
...
author: {
_id: author._id,
username: author.username
}
})
try {
course = await course.save()
res.send(course)
}
catch(er){
console.log(er)
}
})
The error
At line, const author = await Author.find() will return array of authors
while creating course at following lines of you are using author._id which will be undefined, So you have to find a specific Author using findOne (return a single author as object) or you have to use an indexed element of an author like author[0]._id
let course = new Course({
...
author: {
_id: author._id,
username: author.username
}
})

How to pass a parameter in Koa middleware with context?

I am using nodejs koa rest api service. And I want to pass a parameter to validation middleware.
But I also need to pass context.
How can I correctly use middlewares with koa2
//route.js
const Router = require('koa-router')
const auth = require('../middlewares/auth')
const controller = require('../controllers').editorsController
const schemas = require('../schemas/joi_schemas')
const validation = require('../middlewares/validation')
const router = new Router()
const BASE_URL = `/editors`
router.get('/protected', auth, controller.protected)
router.get(BASE_URL, controller.getEditors)
router.post(BASE_URL, auth, validation(schemas.editorPOST, 'body'), controller.addEditor)
module.exports = router.routes()
//validation.js
const Joi = require('joi')
module.exports = (schema, property, ctx, next) => {
const { error } = Joi.validate(ctx.request[property], schema)
console.log(error)
const valid = error == null
if (valid) {
next()
} else {
const { details } = error
const message = details.map(i => i.message).join(',')
ctx.status = 422
ctx.body = {
status: 'error',
message: message
}
}
}
//joi_schemas.js
const Joi = require('joi')
const schemas = {
editorPOST: Joi.object().keys({
username: Joi.string().required(),
password: Joi.string().required(),
enable: Joi.number()
})
}
module.exports = schemas
I get some errors:
Cannot read property 'request' of undefined
Or any other solutions?
ctx.request is undefined because ctx wasn't passed as an argument:
validation(schemas.editorPOST, 'body')
And ctx it's unavailable in the scope where the middleware was called.
If a middleware needs to be parametrized, it should be higher order function:
module.exports = (schema, property) => (ctx, next) => {
...

Node Express - Cannot POST / error

I have been following a tutorial on how to build a full stack MEAN application and everything has been functioning fine up until this point (registration, login and authentication.)
I'm now trying to do a post request for the blog page and am receiving the following error:
<pre>Cannot POST /blogs/newBlog</pre>
All that I've done so far is create a schema, a route and then made the relevant changes to index.js. The schema file provided below is the one provided by the author of the tutorial in his respository (unlike the other two files it is in it's completed form.) The problem still persists and so I do not believe it to be the problem.
Blog schema:
/* ===================
Import Node Modules
=================== */
const mongoose = require('mongoose'); // Node Tool for MongoDB
mongoose.Promise = global.Promise; // Configure Mongoose Promises
const Schema = mongoose.Schema; // Import Schema from Mongoose
// Validate Function to check blog title length
let titleLengthChecker = (title) => {
// Check if blog title exists
if (!title) {
return false; // Return error
} else {
// Check the length of title
if (title.length < 5 || title.length > 50) {
return false; // Return error if not within proper length
} else {
return true; // Return as valid title
}
}
};
// Validate Function to check if valid title format
let alphaNumericTitleChecker = (title) => {
// Check if title exists
if (!title) {
return false; // Return error
} else {
// Regular expression to test for a valid title
const regExp = new RegExp(/^[a-zA-Z0-9 ]+$/);
return regExp.test(title); // Return regular expression test results (true or false)
}
};
// Array of Title Validators
const titleValidators = [
// First Title Validator
{
validator: titleLengthChecker,
message: 'Title must be more than 5 characters but no more than 50'
},
// Second Title Validator
{
validator: alphaNumericTitleChecker,
message: 'Title must be alphanumeric'
}
];
// Validate Function to check body length
let bodyLengthChecker = (body) => {
// Check if body exists
if (!body) {
return false; // Return error
} else {
// Check length of body
if (body.length < 5 || body.length > 500) {
return false; // Return error if does not meet length requirement
} else {
return true; // Return as valid body
}
}
};
// Array of Body validators
const bodyValidators = [
// First Body validator
{
validator: bodyLengthChecker,
message: 'Body must be more than 5 characters but no more than 500.'
}
];
// Validate Function to check comment length
let commentLengthChecker = (comment) => {
// Check if comment exists
if (!comment[0]) {
return false; // Return error
} else {
// Check comment length
if (comment[0].length < 1 || comment[0].length > 200) {
return false; // Return error if comment length requirement is not met
} else {
return true; // Return comment as valid
}
}
};
// Array of Comment validators
const commentValidators = [
// First comment validator
{
validator: commentLengthChecker,
message: 'Comments may not exceed 200 characters.'
}
];
// Blog Model Definition
const blogSchema = new Schema({
title: { type: String, required: true, validate: titleValidators },
body: { type: String, required: true, validate: bodyValidators },
createdBy: { type: String },
createdAt: { type: Date, default: Date.now() },
likes: { type: Number, default: 0 },
likedBy: { type: Array },
dislikes: { type: Number, default: 0 },
dislikedBy: { type: Array },
comments: [{
comment: { type: String, validate: commentValidators },
commentator: { type: String }
}]
});
// Export Module/Schema
module.exports = mongoose.model('Blog', blogSchema);
routes/blogs.js
const User = require('../models/user'); // Import User Model Schema
const jwt = require('jsonwebtoken');
const config = require('../config/database');
module.exports = (router) => {
router.post('/newBlog', (req, res) => { // TODO: change URL
res.send('test worked');
});
return router; // Return router object to main index.js
}
index.js
/* ===================
Import Node Modules
=================== */
const env = require('./env');
const express = require('express');
const app = express();
const router = express.Router();
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const config = require('./config/database');
const path = require('path');
const authentication = require('./routes/authentication')(router);
const blogs = require('./routes/blogs')(router);
const bodyParser = require('body-parser');
const cors = require('cors');
const port = process.env.PORT || 8080;
// Database Connection
mongoose.connect(config.uri, {
useMongoClient: true,
}, (err) => {
// Check if database was able to connect
if (err) {
console.log('Could NOT connect to database: ', err);
message
} else {
console.log('Connected to ' + config.db);
}
});
// Middleware
app.use(cors({ origin: 'http://localhost:4200' }));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static(__dirname + '/public'));
app.use('/authentication', authentication);
app.use('/blogs', blogs);
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/public/index.html'));
});
// Start Server: Listen on port 8080
app.listen(port, () => {
console.log('Listening on port ' + port + ' in ' + process.env.NODE_ENV + ' mode');
});
I have been enjoying this course greatly and would appreciate any help (even if it is to simply rule out possible causes.)
Error in full:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /blogs/newBlog</pre>
</body>
</html>
Your problem has to do with:
app.use('/blogs', blogs);
The blogs function should be a function taking (req, res) but it actually takes (router).
You have two options:
Create a router and pass into blogs, e.g. app.use('/blogs', blogs(router)); or
Add app.post(), e.g.
app.post('/blogs/newBlog', (req, res) => {
res.send('test worked');
});
Check spelling of your route
In my case I was posting from a url that had a missing letter.
I was doing POST /webooks/ but I needed to do POST /webhooks (note the missing h letter).
So it may also be that you misspell your routes.
Just try to change from bodyparser to express means...
instead of
app.use(bodyParser.json())
use app.use(express.json())
Please replace this:
router.post('/newBlog', (req, res) => {
res.send('test worked');
});
With this (on all your methods "get, post, use" etc):
// Make sure to always add a slash both before and after the endpoint!
router.post('/newBlog/', (req, res) => {
res.send('test worked');
});
I encounter this issue mostly if I don't add endpoint slashes properly.

Resources