I am trying to test an Express API POST Route that uses Express Validator for check:
usersRouter.post(
'/',
[
check('name', 'Please enter a name.').not().isEmpty(),
check('email', 'Please enter a valid email.').isEmail(),
check(
'password',
'Please enter a password of 6 characters or more.'
).isLength({ min: 6 }),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log('errors: ', errors);
return res.status(400).json({ errors: errors.array() });
}
const { name, email, password } = req.body;
try {
//...
}
catch {
//...
}
}
);
This API route expects to receive a request consisting of a body that contains the fields, name, email, and password:
const { name, email, password } = req.body
In order to test this route, I have a test file using supertest and jest:
const mongoose = require('mongoose');
const supertest = require('supertest');
const app = require('../app');
const testApi = supertest(app);
const User = require('../models/User');
test('a token is returned', async () => {
// Create a new test user for the HTTP request.
const newTestUser = {
name: 'bob',
email: 'test#test.com',
password: 'newtestpw',
};
const { name, email, password } = newTestUser;
const body = await JSON.stringify({ name, email, password });
// Execute the test.
const config = {
headers: {
'Content-Type': 'application/json',
},
};
let result = await testApi.post('/api/users', body, config);
expect(result.status).toBe(200);
expect(result.headers).toHaveProperty('token');
});
afterAll(async () => {
await mongoose.connection.close();
});
When I execute this test, each check in the POST API route fails. The following errors is returned:
errors: Result {
formatter: [Function: formatter],
errors:
[ { value: undefined,
msg: 'Please enter a name.',
param: 'name',
location: 'body' },
{ value: undefined,
msg: 'Please enter a valid email.',
param: 'email',
location: 'body' },
{ value: undefined,
msg: 'Please enter a password of 6 characters or more.',
param: 'password',
location: 'body' } ] }
Why is the API route not receiving the request I'm sending using Supertest?
Well, it seems that you're not sending your values right.
Look closely at where you send your name, email and password, and how you send them. You can try going to your route and console.log the values it's getting.
And look in how the api.post function actually works. I suggest looking on the Supertest github page and Superagent docs
Just in case you want to try to solve the problem on your own, I hid the solution in the spoiler. But in short, first:
You don't need to stringify your body. You should send it as a usual JavaScript object. Also, you don't need to await the JSON.stringify, as it doesn't return a promise, it's synchronous
Second:
The api.post function only takes the URL as its argument. To send any data you want along with the request you need to chain .send(data) after the .post
Third:
The headers are also set by chaining the .set('Header', 'value') method before or after the .send
So in the end, your request should look something like this.
testApi
.post(url)
.set('Content-Type', 'application/json')
.send(newTestUser)
Related
I've made a fairly simple register/login form for my backend to save to a database. It was working a couple days ago, and now nothing. When I make the fetch call the payload is filled out appropriately. My terminal tells me 'POST /api/v1/users/register 404 13.504 ms - 161'. My fetch request looks like:
const handleRegisterSubmit = async (e) => {
e.preventDefault()
await fetch('http://localhost:3333/api/v1/users/register', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
dealershipname: registerForm.dealershipname,
password: registerForm.password,
streetadress: registerForm.streetadress,
city: registerForm.city,
state: registerForm.state,
zip: registerForm.zip,
phone: registerForm.phone,
email: registerForm.email
})
})
.then(res => res.json())
.then(data => {
if (data.error) {
setError(data.error)
} else {
navigate('/porsche')
}
})
}
My backend route looks like:
router.post('/register', async (req, res) => {
// check for username and password on req
if (!req.body.dealershipname || !req.body.password) {
return res.status(400).json({
error: 'Please include username and password'
})
}
// check database for existing user
const user = await models.User.findOne({
where: {
dealershipname: req.body.dealershipname
}
})
// if exists, send error
if (user) {
return res.json(400).json({
error: 'Username already in use. Pick another'
})
}
// hash password
const hash = await bcrypt.hash(req.body.password, 10)
// create user
const newUser = await models.User.create({
dealershipname: req.body.dealershipname,
password: hash,
streetadress: req.body.streetadress,
city: req.body.city,
state: req.body.state,
zip: Number(req.body.zip),
phone: Number(req.body.phone),
email: req.body.email
})
// respond with success message
return res.status(201).json(newUser)
})
My server is running fine. I've set the backend port to 3333 in case that it just needed some room from the front end port. The errors I'm getting in the browser are the same thing. Any help or suggestions would be a lifesaver.
I've been trying to make an express middleware that sends an email using Nodemailer after the previous middleware finishes. I've come up with a few different designs, but ultimately each different version has it's drawback.
Ultimately, I would like the middleware to have a response from the previous middleware. If it is a success, then send a success email, otherwise, send an error email.
I came up with a dual design where one variation pushes to an error middleware, and a success leads to the next middleware. This contains some slight issues of sending multiple headers, specifically on an the second middleware erroring. I could say, if the mail errors out, do nothing. But that doesn't seem right. If anyone has any suggestions on a good design, that would be great.
From what you described, I would suggest not to create different middleware for that, but to just create one generic email function that would handle different type of messages. Then, just use that function in the first middleware and pass different parameters based on use case (success/error).
email-controller.js
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
},
});
exports.send_email_message = (send_to, subject, message) => {
return new Promise((resolve, reject) => {
const email_message = {
from: { name: process.env.EMAIL_FRIENDLY_NAME },
to: send_to,
subject: subject,
text: message
};
transporter.sendMail(email_message).then(() => {
resolve(true);
}).catch((error) => {
reject(false);
});
})
}
custom-router.js
const { send_email_message } = require('./email-controller');
const express = require('express');
const router = express.Router();
router.get('/custom-middleware', async (req, res, next) => {
try {
// You can calculate "success" variable based on your custom logic
if(success){
await send_email_message('example#gmail.com', 'Success', 'This is body of success message.');
return res.status(200).json({ success: true });
} else {
await send_email_message('example#gmail.com', 'Error', 'This is body of error message.');
return res.status(400).json({ success: false });
}
} catch(error) {
return res.status(400).json({ success: false });
}
});
module.exports = router;
I am currently trying to create a login system with a Mongo Database, but it won't work when I try to fetch POST the login credentials to my express.js API via the Chrome Browser. Unlike in any browser itt works when I use the Insomnia Client. I personally think the problem is either in the header or middleware part of the code. I am grateful for every indication where my problem might be.
Code:
Login function:
async function login() {
const data = getUserDataEncrypted(); //gets username and password
await fetch(url + "/checkUser/login", {
method: "POST",
mode: 'cors',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then(res => {
console.log(res.json());
});
}
Used Middleware:
require('dotenv').config();
const express = require("express");
const app = express();
const mongoose = require('mongoose');
app.use(require("cors")());
app.use(express.json());
app.use(require("morgan")("combined"));
Server Side:
router.post('/login', async (req, res) => {
try {
const user = await User.find({ username: req.body.username });
if (user[0].password === req.body.password) {
res.send({
message: "Successfull Login",
login: true
});
return;
} else {
res.send({
message: "Password incorrect",
login: false
});
return;
}
} catch (error) {
res.send({
message: error.message,
req: req.body
});
}
});
User Data:
async function getUserDataEncrypted() {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
password = await SHA256Encyption(password);
const data = {
username: username,
password: password
}
return data;
}
Images:
In the login function, try to console.log the data from getUserDataEncrypted().
If its null or undefined try to use await.
await getUserDataEncrypted();
const data = getUserDataEncrypted(); //gets username and password
You need to do some basic debugging. If you had logged the value of data then the problem would be obvious.
getUserDataEncrypted is marked as async so it returns a promise.
console.log(JSON.stringify(Promise.resolve()));
Promises stringify to empty objects.
You need to await the promise or get (and use) its result in a then() callback.
I am making a web application that allows Fortnite players to find other players to play with. Users should be able to register, login, post and comment. I have designed the frontend portion of the user login and registration features as well as the backend of the user registration but one of my requirements is that:
Before registration, the server should check whether the username provided is a real Fortnite username using the FortniteTracker API which provides user profiles using their very simple API.
Example Call: GET https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}
How do I verify the username exists before allowing the user to create the account?
I have tried creating a separate endpoint for the API call from the server side but I didn't know how to implement it into my /register endpoint
script.js
function registerRequest(username,password) {
$.ajax({
url: "http://localhost:8080/register",
type: 'POST',
data: JSON.stringify({username,password}),
contentType: "application/json",
error : function(err) {
console.log('Error here!', err)
},
success: function(data) {
console.log('Success!')
// What do I put here?
}
});
}
function handleRegisterSubmit(event) {
event.preventDefault();
const username = $(event.currentTarget).find('.username-register').val()
const password = $(event.currentTarget).find('.password-register').val()
const passwordConfirm = $(event.currentTarget).find('.password-confirm').val()
if (password === passwordConfirm) {
registerRequest(username,password)
}
else {
console.error("Passwords did not match")
}
}
$(function onLoad() {
displayRegisterPage()
$(`.js-content-section`).on('submit', '.js-register-form', handleRegisterSubmit)
}
})
server.js
app.post('/register', jsonParser, (req, res) => {
const requiredFields = ['username', 'password']
for (let i = 0; i < requiredFields.length; i++) {
const field = requiredFields[i]
if (!(field in req.body)) {
const message = `Missing \`${field}\` in request body`
console.error(message)
return res.status(400).send(message)
}
}
let username = req.body.username;
let password = req.body.password;
User.findOne({username})
.then(user => {
if (user) {
const message = `username is already taken`
console.error(message)
return res.status(400).send(message)
}
else {
User.create({username, password})
.then(user => {
const userRes = {
id: user._id,
username: user.username
}
res.status(201).json(userRes)
}
)
}
})
.catch(err => {
console.error(err)
res.status(500).json({ error: 'something went horribly wrong'})
})
})
app.get('/login', (req, res) => {
const usernameReq = User.findById(req.body.username);
if (usernameReq) {
console.log(usernameReq)
res.status(201).json(usernameReq)
}
})
schema.js
const UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true,
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
I expect that if I register with "ninja" as a username I should be able to register since that is a valid Fortnite username. The actual output currently allows users to register with any username that isnt already taken in the database.
You would need packages like axios, request, request-promise (Promise supported version of request) etc to make the external api call. You can try implementing within the register like.
const rp = require('request-promise');
app.post('/register', jsonParser, async (req, res) => {
...
let username = req.body.username;
let password = req.body.password;
const options = {
method : 'GET',
uri: 'https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}',
resolveWithFullResponse: true
}
const data = await rp(options)
// check if response code is 200 and check for the expected body
...
// continue rest of the code
}
Or have another middleware to call the external endpoint and do the checks like:
async function checkUser (req, res, next) {
const options = {
method : 'GET',
uri: 'https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}',
resolveWithFullResponse: true
}
const data = await rp(options)
// check if response code is 200 and check for the expected body
if (checks ok)
// if all check ok go to next middleware
next()
else
// if checks did not succeed
// you could pass error to error handler like next(new Error("Hey you do not exist"))
// or render something here
}
Then mount it like:
app.post('/register', jsonParser, checkUser, (req, res) {
...
You can do it simply by sending the username to the API https://api.fortnitetracker.com/v1/profile/{platform}/{epic-nickname}
It will give you a response mentioning about the user exists or not. Based on the response you can make another AJAX request to register the user only if the user does not exist.
I use a Promise request to resolve, reject when someone enters their username. It is only called onClick. in your request you will be able to determine if the call was successfull or not with the username.
I'm trying to use Node JWT Authentication API to build a local API using the following git: https://github.com/cornflourblue/node-role-based-authorization-api
the server listens in 4000 port, but it returns me the error 'Invalid token'. why is this happening?
I have the version 1.17.5
const config = require('config.json');
const jwt = require('jsonwebtoken');
// users hardcoded for simplicity, store in a db for production applications
const users = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }];
module.exports = {
authenticate,
getAll
};
async function authenticate({ username, password }) {
const user = users.find(u => u.username === username && u.password === password);
if (user) {
const token = jwt.sign({ sub: user.id }, config.secret);
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
token
};
}
}
async function getAll() {
return users.map(u => {
const { password, ...userWithoutPassword } = u;
return userWithoutPassword;
});
}
Use Postman to send a POST(this is important. It should be POST) request to localhost:4000/users/authenticate. In the Body Tab change "form-data" to "raw" and type:
{
"username":"admin",
"password":"admin"
}
You will get token. Copy it.
Result of the POST request
Open a new tab to make a new GET request to localhost:4000/users/. On the Headers tab of Postman enter "Authorization" in the key field and 'bearer [token you copied]' to Value field. Make the request. It should return json with users.
Result of the GET request