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) => {
...
Related
made an API for Social media Application to get timeline posts on the app, I requested from Thunder Client
request query is like: http://localhost:8000/post/63d4051a252ef6c3d50d7f17/timeline, where userId is this :63d4051a252ef6c3d50d7f17,
i did log userId and correctly got Data Document from mongoDB
try {
const currentUserPost = await PostModel.find({ userId: userId });
console.log( currentUserPost )
}
but still getting empty object {} error is: Internal server error status code 500 on thunder client,
check out once below code, and let me know what's wrong and why. it seems good to me but it isn't working.
PostControler.js
const PostModel = require("../Models/Postmodels");
const mongoose = require("mongoose");
const UserModel = require("../Models/Usermodel");
/ get timeLine post
const getTimeLinePost = async (req, res) => {
const userId = req.params.id;
try {
const currentUserPost = await PostModel.find({ userId: userId });
console.log( currentUserPost )
const followingPost = await UserModel.aggregate([
{
$match: {
_id: new mongoose.Types.ObjectId(userId),
}
},
{
$lookup: {
from: "posts",
localField: "following",
foreignField: "userId",
as: "followingPosts",
}
},
{
$project: {
followingPost: 1,
_id: 0,
}
},
]);
res.status(200).json(currentUserPost.concat(...followingPost[0].followingPost)
.sort((a,b)=>{
return b.createdAt - a.createdAt
}));
} catch (error) {
res.status(500).send(error);
}
};
module.exports = { createPost,getPost, updatePost, deletePost, likesDislikesPost, getTimeLinePost };
PostRoute.js
const express = require("express");
const {createPost,getPost, updatePost, deletePost, likesDislikesPost, getTimeLinePost} = require("../Controller/PostControler");
const router = express.Router();
router.post('/', createPost)
router.get('/:id', getPost)
router.put('/:id', updatePost)
router.delete('/:id', deletePost)
router.put('/:id/like', likesDislikesPost)
router.get('/:id/timeline', getTimeLinePost)
module.exports = router
index.js
const express = require("express")
require('dotenv').config()
const bodyparser = require("body-parser")
const mongoose = require("mongoose")
const app = express();
const Authroute = require("./Routes/Authroute")
const UserRoute = require("./Routes/userRout")
const PostRout = require("./Routes/PostRoute")
.
.
.
.
.
.
app.use("/auth",Authroute)
app.use("/user",UserRoute)
app.use("/post", PostRout)
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);
});
My console shows the following error:
POST /log-in 200 637.310 ms - 42
Unhandled rejection TypeError: models.UserAccount.setJwTokenCookie is not a function
at models.UserAccount.findOne.then.userRecord (/home/owner/PhpstormProjects/reportingAreaApi/routes/index.js:99:33)
at tryCatcher (/home/owner/PhpstormProjects/reportingAreaApi/node_modules/bluebird/js/release/util.js:16:23)
I have been attempting to add instance methods to the UserAccount model definition. The method setJwTokenCookie appears to be at fault below:
'use strict';
require('dotenv').load();
const bcryptjs = require('bcryptjs'),
jsonWebToken = require('jsonwebtoken'),
moment = require('moment');
module.exports = (sequelize, DataTypes) => {
const UserAccount = sequelize.define('UserAccount', {
username: DataTypes.STRING,
password: DataTypes.STRING
});
// cookie-setter
UserAccount.prototype.setJwTokenCookie = (responseLocal, userId) => {
// generate a new jwt encoded with userId:
const signedToken = jsonWebToken.sign({
data: {
userId : userId
}
}, <secret>);
const dateIn10Years = new moment()
.add(10, "years").toDate();
responseLocal.cookie('jwTokenCookie', signedToken, {
httpOnly: true,
expires : dateIn10Years
})
};
return UserAccount;
};
I am trying to follow the instance method format shown here: http://docs.sequelizejs.com/manual/tutorial/models-definition.html#expansion-of-models
Does anyone know the source of this error? I am working inside of an Express.js project and the error fires when a post request is made to the relevant route handler.
The method is called here:
router.post('/log-in', passport.authenticate('local'), (req, res) => {
// get the user's Id. Then set the cookie with it
models.UserAccount.findOne({ where : { username : req.body.username }})
.then(userRecord => {
const { userId } = userRecord;
return models.UserAccount.setJwTokenCookie(res, userId);
});
You have defined it as a instance method:
UserAccount.prototype.setJwTokenCookie
But are calling it as a class method:
models.UserAccount.setJwTokenCookie
It should just be:
.then(userRecord => {
const { userId } = userRecord;
return userRecord.setJwTokenCookie(res, userId);
});
I'm trying to test a service that uses the #google/maps client for getting directions data.
Here is a simplified version of the service:
'use strict'
const dotenv = require('dotenv')
const GoogleMaps = require('#google/maps')
dotenv.config()
const {GOOGLE_API_KEY: key} = process.env
const client = GoogleMaps.createClient({key, Promise})
const getData = exports.getData = async function({origin, destination}) {
try {
const options = {
origin,
destination,
mode: 'transit',
transit_mode: ['bus', 'rail']
}
const res = await client.directions(options).asPromise()
return res
} catch (err) {
throw err
}
}
And here is a test file to show the case:
'use strict'
const dotenv = require('dotenv')
const nock = require('nock')
const gdService = require('./gd.service')
dotenv.config()
const {GOOGLE_API_KEY: key} = process.env
const response = {json: {name: 'custom'}}
const origin = {lat: 51.5187516, lng: -0.0836314}
const destination = {lat: 51.52018, lng: -0.0998361}
const opts = {origin, destination}
nock('https://maps.googleapis.com')
.get('/maps/api/directions/json')
.query({
origin: `${origin.lat},${origin.lng}`,
destination: `${destination.lat},${destination.lng}`,
mode: 'transit',
transit_mode: 'bus|rail',
key
})
.reply(200, response)
gdService.getData(opts)
.then(res => {
console.log(res.json) // it's undefined!
})
.catch(err => {
console.error(err)
})
What I expect is to get the defined response as a response of the service method invocation. But I get undefined. Why is that?
After reading the source of #google/maps client, I figured out that I had to provide nock with the following reply header:
...
nock('https://maps.googleapis.com')
.defaultReplyHeaders({
'Content-Type': 'application/json; charset=UTF-8'
})
.get('/maps/api/directions/json')
...
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}!`))