This is one simple api using express where the results are stored in a course array. The validation logics are stored into the validateCourse function.
Now the problem is the POST endpoint is working correctly but while I'm trying to update through the PUT endpoint i'm getting 404 error:
name is required.
Can anyone tell me where is the problem in the logic or the PUT endpoint thus the update option is not working.
const Joi = require('joi');
const express = require('express');
const app = express();
app.use(express.json());
const courses = [
{ id: 1, name: 'course1' },
{ id: 2, name: 'course2' },
{ id: 3, name: 'course3' }
]
app.post('/api/courses', (req, res) => {
const result = validateCourse(req.body);
// console.log(result);
if (result.error) {
res.status(400).send(result.error.details[0].message);
return;
}
const course = {
id: courses.length + 1,
name: req.body.name
};
courses.push(course);
res.send(course);
});
app.put('/api/courses/:id', (req, res) => {
//Search course by get method
//If not found return error 404
const course = courses.find(c => c.id === parseInt(req.params.id));
if (!course) res.status(404).send('not found with the given id');
//validate
//if invalid ,return 400 bad request
const result = validateCourse(req.body);
// const {error} = validateCourse(req.body);
if (result) {
res.status(400).send(result.error.details[0].message);
console.log(result.error.details);
return;
}
//if valid then update course
//Return the updated course to client
course.name = req.body.name;
res.send(course);
});
//INPUT VALIDATION//
function validateCourse(course) {
const schema = {
name: Joi.string().min(3).required()
};
return Joi.validate(course, schema);
}
Related
Currently I'm working in a project where I'm trying to build a service in express which calls another two external EP. I have a trouble here, because express shows me an error that I can't understand. But I suppose, the way I'm working it should be wrong.
So
app.get("/items/:id", (req, res) => {
return request.get({
url: `https://myapi.com/${req.params.id}`,
json: true
},
(error, response) => {
if(error) {
return res.send("Error ocurred");
}
const itemDesc = request.get({ // Here I'm trying to do the second call and use it later
url: `https://myapi.com/${req.params.id}/description`,
json: true
},
(error, responseDesc) => {
return responseDesc
});
const itemDetails = response.body;
const strPrice = itemDetails.price.toString().split('.');
const numberPrice = parseInt(strPrice[0]);
const floatPrice = strPrice[1] ? parseInt(strPrice[1]) : 00;
return res.send({
id: itemDetails.id,
title: itemDetails.title,
price: {
currency: itemDetails.currency_id,
amount: numberPrice,
decimals: floatPrice,
},
picture: itemDetails.pictures[0].url,
condition: itemDetails.condition,
free_shipping: itemDetails.shipping.free_shipping,
sold_quantity: itemDetails.sold_quantity,
description: itemDesc // Here I'm using the variable of the previous request
});
});
});
Basically, the error I get is that I can't do two calls. I know that because if I remove the nested request, it works.
The error I get is the following:
My question is: Is there any way to do two external request inside the same method?
Thanks in advance
it's cleaner if you do it with async await in your case.
modify your code like this
app.get("/items/:id", async(req, res) => {
try {
const promise1 = fetch(`https://myapi.com/${req.params.id}`).then(data => data.json())
const promise2 = fetch(`https://myapi.com/${req.params.id}/description`)
const [itemDetails, itemDesc] = await Promise.all([promise1, promise2])
const strPrice = itemDetails.price.toString().split('.');
const numberPrice = parseInt(strPrice[0]);
const floatPrice = strPrice[1] ? parseInt(strPrice[1]) : 00;
res.send({
id: itemDetails.id,
title: itemDetails.title,
price: {
currency: itemDetails.currency_id,
amount: numberPrice,
decimals: floatPrice,
},
picture: itemDetails.pictures[0].url,
condition: itemDetails.condition,
free_shipping: itemDetails.shipping.free_shipping,
sold_quantity: itemDetails.sold_quantity,
description: itemDesc // Here I'm using the variable of the previous request
});
} catch (
res.send("Error ocurred")
)
});
I'm having a problem right now when i want to remove some code out of my route to put it into a service. I'm just trying to follow the best practices of developing an application.
This is my route right now:
const express = require('express');
const cityRouter = express.Router();
const axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
cityRouter.get('/:cep', async (request, response) => {
try {
const { cep } = request.params;
const value = myCache.get(cep);
if (value) {
response.status(200).send({
city: value,
message: 'Data from the cache',
});
} else {
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
response.status(200).send({
city: resp.data,
message: 'Data not from the cache',
});
}
} catch (error) {
return response.status(400);
}
});
module.exports = cityRouter;
I'm using axios to retrieve data from an API, where i have a variable called "cep" as a parameter and then using node-cache to cache it.
And it works with out problems:
enter image description here
But, when i try to put the same code into a service, and then call it into my route:
My service:
const axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
function verificaCache(cep) {
return async function (request, response, next) {
const value = myCache.get(cep);
console.log(cep);
if (value) {
response.status(200).send({
city: value,
message: 'Data from the cache',
});
} else {
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
response.status(200).send({
city: resp.data,
message: 'Data not from the cache',
});
}
next();
};
}
module.exports = verificaCache;
My route using the service:
const express = require('express');
const cityRouter = express.Router();
const verificaCache = require('../services/VerificaCacheService');
cityRouter.get('/:cep', async (request, response) => {
const { cep } = request.params;
verificaCache(cep);
response.status(200);
});
module.exports = cityRouter;
By some reason, it doesn't work:
enter image description here
What is the problem that i can't see? I'm a beginner so i'm kinda lost right now.
You have created a high-order function by returning a function in verificaCache(), so to properly call it you need to do it like that await verificaCache(cep)(req, res), remember, the first time you call it, you have a function being returned, since you want the tasks inside of that function to be executed, you need to call it as well.
Take a reading about high-order functions here: https://blog.alexdevero.com/higher-order-functions-javascript/
My recommendation, you could just get rid of the other function you are returning to simplify your code, and let the service only handle business logic, all the http actions should be handled on the controller level:
// Service
function verificaCache(cep) {
const value = myCache.get(cep);
if (value) {
return { city: value, message: 'Data from the cache'})
}
// No need of an else statement because the
// execution will stop at the first return if the condition passes
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
return { city: resp.data, message: 'Data not from the cache'};
}
// Controller
cityRouter.get('/:cep', async (request, response) => {
const { cep } = request.params;
try {
const data = verificaCache(cep);
// Use json() instead of send()
response.status(200).json(data);
} catch(error) {
// Handle errors here
console.log(error);
}
});
Estamos juntos!
I am using node.js, MySQL, knex, and express.
I am doing a simple query of a database, db.findAllEmoji().
const findAllEmoji = () => {
return knex('emoji')
.select('*');
};
I am working from previous code that works I am modeling after, but am still stuck. There are two large code blocks below The first is from my routes in which I render the page in routes\dashboard.js. The second is what I am modeling after.
What I have below in the first large code block returns undefined unless I use let query = await db.findAllEmoji();, only then will it return the results of the query. That would be fine, but...if I use await, then the .whereRaw and .orderBy throws these errors and I have not been able to get past these. Here's one of them.
TypeError: query.orderBy is not a function at C:\Users\pauli\repos\all-coursework-node-paulwinka\tests\02. MySQL database schema emoji\routes\dashboard.js:21:21 at processTicksAndRejections (internal/process/task_queues.js:97:5)
My sample code did not need await to work, so I would prefer a solution that figures out why my query doesn't work without await..or maybe.
So my questions are, why won't the original query not work without await...and how can I get it to work without await like in my model code?
And if I just must use await in this case, how can I fix the errors with orderBy not working?
I've only been using these for a few weeks and am still learning the ropes. Thanks. :)
const express = require('express');
const db = require('../db');
const debug = require('debug')('app:routes:dashboard');
// creating instance of router.
const router = express.Router();
router.use(express.urlencoded({ extended: false }));
router.use(express.json());
router.get('/', async (req, res, next) => {
try {
const search = req.query.search;
let query = db.findAllEmoji();
if (search) {
query = query.whereRaw('description LIKE ?', ['%' + search + '%']);
} else {
query = query.orderBy('emoji_id');
}
debug(`query length: ${query.length}`);
res.render('dashboard/dashboard-user', {
title: 'Dashboard - Emoji',
active: 'dashboard',
query,
});
} catch (err) {
next(err);
}
});
module.exports = router;
This is the code that did work that I am modeling after...maybe I am missed something obvious.
try {
const industry = req.query.industry;
const search = req.query.search;
const pageSize = parseInt(req.query.pageSize) || 10;
const pageNumber = parseInt(req.query.page) || 1;
const industryOptionList = {
selected: industry || '',
options: [
{ value: '', text: 'All Categories' },
{ value: 'hospitality', text: 'Hospitality' },
{ value: 'foodservice', text: 'Foodservice' },
{ value: 'IT', text: 'IT' },
{ value: 'defense', text: 'Defense' },
{ value: 'finance', text: 'Finance' },
{ value: 'construction', text: 'Construction' },
],
};
let query = db.getAllListings();
if (industry) {
query = query.where('company.industry', industry);
}
if (search) {
// query = query.whereRaw('MATCH (review.title, review.review_text) AGAINST (? IN NATURAL LANGUAGE MODE)', [search]);
query = query.whereRaw(
'listing_title LIKE ? OR company.industry LIKE ? OR company_name LIKE ? OR company_description LIKE ?',
['%' + search + '%', '%' + search + '%', '%' + search + '%', '%' + search + '%']
);
} else {
query = query.orderBy('posted_date');
}
const pager = await pagerUtils.getPager(query, pageSize, pageNumber, req.originalUrl);
const listings = await query.limit(pageSize).offset(pageSize * (pageNumber - 1));
debug(`listings length is ${listings.length}`);
if (!req.xhr) {
res.render('listing/listing-list', {
title: 'Jobify: Listings',
listings,
industry,
industryOptionList,
search,
pager,
});
} else {
res.render('listing/search-results', { listings, pager: pager, layout: null });
}
} catch (err) {
next(err);
}
});
Here is my ..db code too in case it helps.
// get connection config
const config = require('config');
const { sum } = require('lodash');
const databaseConfig = config.get('db');
const debug = require('debug')('app:server');
//connect to the database
const knex = require('knex')({
client: 'mysql',
connection: databaseConfig,
});
const findAllEmoji = () => {
return knex('emoji')
.select('*');
};
module.exports = {
knex,
findAllEmoji,
};
If you are not awaiting or calling .then() for query builder, the query gets only built, but it well never be executed.
Query builder works in a way that you can add more .where, .join etc. parts to the query in different lines of code (like you are doing in router.get('/', async (req, res, next) => {) and when the query is ready it needs to be executed so that it will only then sent to DB sever to get response.
I want to create a schema, with arrays for names of participants at an event, I make the list of participants by doing so:
quizPart:[{
type:String,
}]
How can I validate that the length of this array is either zero (no participants at this event) or 2, and not 1 (it is a two people per team event). I want to return an error message that I can handle with ValidationError
I am adding data to this schema like so:
var school = new School();
school.quizPart=req.body.quiz;
where req.body.quiz = ["name1","name2"] or ['','']
and then, if only 1 field has a string value, I want to parse an error to the repsonse body like so:
function handleValidationError(err, body) {
for (field in err.errors) {
switch (err.errors[field].path) {
case "quizPart":
body["quizPartError"] = err.errors[field].message;
break;
}}}
This is a working example of what I mean to say.
Write a pre('update') mongoose hook and inspect the $set object if the quizParts field has length 0 or 2 or not.
index.js
const mongoose = require('mongoose');
const test = require('./test');
mongoose.connect('mongodb://localhost:27017/test2', {useNewUrlParser: true});
mongoose.set('debug',true);
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
// we're connected!
});
(async() => {
try {
const testUpdate = test();
const updateQuery = {
$set: {
quizPart: [
{
type: 'Type 1'
},
{
type: 'Type 2'
}
]
}
};
const updateResult = await testUpdate.update({}, updateQuery).exec();
} catch(err) {
console.error(err);
}
})();
test.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
module.exports = function() {
const testSchema = new Schema({
quizPart: [
{
type: String,
}
]
},
{
collection: 'test',
timestamps: true
});
testSchema.pre('update', function(next) {
const update = this._update.$set;
if (update.length === 0 || update.length === 2) {
return next();
}
else {
return next(new Error('Cannot have length 1!'));
}
});
return mongoose.model('test', testSchema);
};
Made the field:
quizPart:[{
type:String,
}],
And then verified the field by:
schoolSchema.path('quizPart').validate((list)=>{
alNumRegex= /^[a-z0-9]+$/i
return list[0]!=="" && list[1]!=="" || alNumRegex.test(list[0]) && alNumRegex.test(list[1]);
},'Please Register atleast two participants for quiz.');
I'm creating API tests with async-await using Supertest and Mocha.
In the accountsData.js file I created a function to generate random test accounts.
In the accountsHelper.js file I created a function to create unlimited accounts using a while loop
When I run tests on the post_accounts.js file, the first account is created successfully, but from the second account, the data generated in the accountsData.js file is already repeated.
Why isn't data randomly generated when I create more than one account using data from the accountsData.js file?
accountsData.js
const casual = require('casual');
function randomAccount() {
return {
'email': casual.email,
'password': '123456',
};
}
module.exports = {
randomAccount,
};
accountsHelper.js
const request = require('supertest');
const commonData = require('../data/commonData');
/* eslint-disable no-console */
const accountList = [];
let counterAccounts;
module.exports = {
async createAccount(account, accountsToCreate = 2, validateResponse = true) {
counterAccounts = 0;
while (counterAccounts < accountsToCreate) {
try {
const res = await request(commonData.environment.staging)
.post(commonData.endpoint.accounts)
.send(account);
if (validateResponse === true) {
if (res.status === commonData.statusCode.ok) {
accountList.push(res.body);
} else {
throw new Error('Email already exists\n\n' + JSON.stringify(res.body, null, ' '));
}
} else {
return res.body;
}
} catch (err) {
console.log(err);
}
counterAccounts++;
}
return accountList;
},
};
post_accounts.js
const accountsData = require('../../data/accountsData');
const accountsHelper = require('../../helpers/accountsHelper');
const account = accountsData.randomAccount();
describe('Create accounts with email and password', () => {
context('valid accounts', () => {
it('should create an account successfully', async() => {
const res = await accountsHelper.createAccount(account);
// eslint-disable-next-line no-console
console.log(res);
});
});
});
API response:
Create accounts with email and password
valid accounts
Error: Email already exists
{
"error": {
"statusCode": 422,
"name": "ValidationError",
"message": "The `account` instance is not valid. Details: `email` Email already exists (value: \"Lemuel.Lynch#Susan.net\").",
"details": {
"context": "account",
"codes": {
"email": [
"uniqueness"
]
},
"messages": {
"email": [
"Email already exists"
]
}
}
}
}
at Object.createAccount (/Users/rafael/Desktop/projects/services/test/helpers/accountsHelper.js:24:19)
at process._tickCallback (internal/process/next_tick.js:68:7)
[ { 'privacy-terms': false,
'created-date': '2019-08-24T10:00:34.094Z',
admin: false,
isQueued: false,
lastReleaseAttempt: '1970-01-01T00:00:00.000Z',
'agreed-to-rules': { agreed: false },
email: 'Lemuel.Lynch#Susan.net',
id: '5d610ac213c07d752ae53d91' } ]
✓ should create an account successfully (2243ms)
1 passing (2s)
The code that you posted doesn't correspond to the code that you're describing in prose.
However, I tested your accountsData.js file, in the way that your words (but not your code) say that you're using it, and it works fine.
// main.js
const { createPerson } = require(__dirname + '/accountsData')
console.log(createPerson())
console.log(createPerson())
console.log(createPerson())
console.log(createPerson())
console.log(createPerson())
Output from running it once:
$ node main.js
{ email: 'Anne_Ebert#Macie.com', password: '123456' }
{ email: 'Manley.Lindgren#Kshlerin.info', password: '123456' }
{ email: 'McClure_Thurman#Zboncak.net', password: '123456' }
{ email: 'Breitenberg.Alexander#Savannah.com', password: '123456' }
{ email: 'Keely.Mann#Stark.io', password: '123456' }
And again:
$ node main.js
{ email: 'Destany_Herman#Penelope.net', password: '123456' }
{ email: 'Narciso_Roob#gmail.com', password: '123456' }
{ email: 'Burnice_Rice#yahoo.com', password: '123456' }
{ email: 'Roma_Nolan#yahoo.com', password: '123456' }
{ email: 'Lilla_Beier#yahoo.com', password: '123456' }
Nothing in the code that you posted is actually requiring or using accountsData.js. If you change your code to use it, I think you'll see, like I do, that it works.
Problem is, you are generating the random account and storing it in a variable 'post_accounts.js(line 3)'. So, when you create an account, you are using the same payload to create multiple accounts, which obviously throws an error.
I just modified the accountHelper to properly handle your scenario. Hope this helps.
Note: The code is not tested, I just wrote it from my mind. Please test and let me know if it works.
// accountsHelper.js
const request = require('supertest');
const commonData = require('../data/commonData');
const accountsData = require('../../data/accountsData');
/* eslint-disable no-console */
const accountList = [];
module.exports = {
async createAccount(account, accountsToCreate = 1, validateResponse = true) {
// creates an array of length passed in accountsToCreate param
return (await Promise.all(Array(accountsToCreate)
.fill()
.map(async () => {
try {
const res = await request(commonData.environment.staging)
.post(commonData.endpoint.accounts)
// takes account if passed or generates a random account
.send(account || accountsData.randomAccount());
// validates and throw error if validateResponse is true
if (validateResponse === true && (res.status !== commonData.statusCode.ok)) {
throw new Error(
'Email already exists\n\n' +
JSON.stringify(res.body, null, ' ')
);
}
// return response body by default
return res.body;
} catch (e) {
console.error(e);
// return null if the create account service errors out, just to make sure the all other create account call doesnt fail
return null;
}
})))
// filter out the null(error) responses
.filter(acc => acc);
}
};
//post_accounts.js
const accountsHelper = require('../../helpers/accountsHelper');
const accountsData = require('../../data/accountsData');
const GENERATE_RANDOM_ACCOUNT = null;
describe('Create accounts with email and password', () => {
context('valid accounts', () => {
it('should create an account successfully', async () => {
const result = await accountsHelper.createAccount();
expect(result.length).toEquals(1);
});
it('should create 2 accounts successfully', async () => {
const result = await accountsHelper.createAccount(GENERATE_RANDOM_ACCOUNT, 2);
expect(result.length).toEquals(2);
});
it('should not create duplicate accounts', async () => {
const account = accountsData.randomAccount();
// here we are trying to create same account twice
const result = await accountsHelper.createAccount(account, 2);
// expected result should be one as the second attempt will fail with duplicate account
expect(result.length).toEquals(1);
});
});
});