I am writing a bulk import function for a password manager for myself and I have come across an issue.
There is an array of passwords to import and I'm using a forEach() method to iterate through each password to import.
I call the insert function and everything just stops. No error, no callback, no saving to file. Here is my code:
const express = require('express')
const app = express()
const { encrypt, decrypt } = require('./crypto')
const Datastore = require('nedb')
app.post('/bulkimport', checkAuthenticated, (req, res) => {
var passwords = JSON.parse(req.body.passwords)
var dbForUser = new Datastore('./passwords/' + req._passport.session.user + '.db')
passwords.forEach(password => {
function doc(code, link, name, password) {
this.code = code
this.link = link
this.name = name
this.password = password
}
var entry = new doc(password.name, password.url, password.username, password.password)
console.log(entry)
console.log('before insert') // gets logged
dbForUser.insert(entry, function(err, doc) {
console.log('after insert') // doesn't get logged
if (err) return res.status(500).send()
console.log(doc)
})
});
})
Middlewares I'm using:
app.use(bodyParser.json())
app.use(express.urlencoded({ extended: false }))
app.use(flash())
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
app.use(methodOverride('_method'))
Thanks for the help!
I see two problems. db.insert according to the nedb docs takes a plain JS object, and you're calling a res.send() in a forEach (which could result in the cannot set headers after they are sent error. You can also skip parsing the body by using a module for that.
const express = require('express')
// added
const bodyParser = require('body-parser')
const app = express()
const { encrypt, decrypt } = require('./crypto')
const Datastore = require('nedb')
// added
app.use(bodyParser.json())
app.post('/bulkimport', checkAuthenticated, (req, res) => {
// changed
var passwords = req.body.passwords
var dbForUser = new Datastore('./passwords/' + req._passport.session.user + '.db')
// changed: forEach would just keep running, potentially causing errors
for (let password of passwords) {
// changed
var entry = {
link: password.url,
password: password.password,
name: password.username,
code: password.name,
}
// changed to remove res.send from callback
let e = false
dbForUser.insert(entry, (err, doc) => {
if (err) {
e = true
}
})
// added to exit from route if an error
if (e) {
res.status(500).send()
return
}
}
res.status(201).send()
})
EDIT:
In the chat we also discovered that nedb doesn't really do anything on disk unless you call it with autoload: true or call loadDatabase, which was happening on one of the databases but not the one in the Express route (docs). Adding that got things working.
Related
I'm currently learning angular and working on a project with a mongoDB database and express for my APIs. I want to fetch the comments of a post by the post ID,
The get request returns me a list of comments. the problem is when I first run node js the get request doesn't work, it only works when I first post a new comment and then run the get request for the comments again.
And as long as node is running the get request will continue to work whenever it's called for, until I restart node once again for the error to happen again.
it returns a 404 not found error.
This error doesn't happen with any other route, but my code is the same in all of them.
PS : I Have made sure that the function is getting the post id before the get request is made.
this is my server.js file
let express = require('express'),
path = require('path'),
mongoose = require('mongoose'),
cors = require('cors'),
bodyParser = require('body-parser'),
dbConfig = require('./database/db');
//create Error definition
const createError = require('http-errors');
// Connecting with mongo db
mongoose.Promise = global.Promise;
mongoose.connect(dbConfig.db, {
useNewUrlParser: true
}).then(() => {
console.log('Database sucessfully connected')
},
error => {
console.log('Database could not connected: ' + error)
}
)
const userRoute = require('./routes/user.route');
const postRoute = require('./routes/post.route');
const galleryRoute = require('./routes/Gallery.route');
const likeRoute = require('./routes/Like.Route');
const commentRoute = require('./routes/Comment.route');
const shareRoute = require('./routes/Share.route');
const profilePicRoute = require('./routes/ProfilePic.route');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cors());
app.use(express.static(path.join(__dirname, 'dist/mean-stack-crud-app')));
app.use('/', express.static(path.join(__dirname, 'dist/mean-stack-crud-app')));
app.use('/api/users', userRoute);
app.use('/api/posts', postRoute);
app.use('/api/likes', likeRoute);
app.use('/api/profilePics', profilePicRoute);
app.use('/api/comments', commentRoute);
app.use('/api/shares', shareRoute);
app.use('/api/gallery', galleryRoute);
// Create port
const port = process.env.PORT || 4000;
const server = app.listen(port, () => {
console.log('Connected to port ' + port)
})
// Find 404 and hand over to error handler
app.use((req, res, next) => {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
console.error(err.message); // Log error message in our server's console
if (!err.statusCode) err.statusCode = 500; // If err has no specified error code, set error code to 'Internal Server Error (500)'
res.status(err.statusCode).send(err.message); // All HTTP requests must have a response, so let's send back an error with its status code and message
});
this is my commentRoute.js
const express = require('express');
const commentRoute = express.Router();
// Comment model
let Comment = require('../models/Comment');
const createError = require('http-errors');
//multer for pic upload
const uploadMedia = require('../middleware/picUpload')
// Add Comment
commentRoute.route('/create').post((req, res, next) => {
// if(req?.files[0]){
// newComment.media = req?.files[0]
// }
let newComment = req.body;
newComment.creationDate = new Date(req.body.creationDate)
console.log(newComment)
Comment.create(newComment, (error, data) => {
// if (error instanceof multer.MulterError ) {
// error.message += "\nmulter Error";
// return next(error)
// }else
if (error){
return next(error)
}
else {
res.json(data);
}
})
//Get comments by parent ID
commentRoute.route('/read/byParentId/:idParent').get( async (req, res, next) => {
await Comment.find({idParent : req.params.idParent}, (error, data) => {
if(error){
return next(error)
}else{
res.json(data)
}
})
})
})
module.exports = commentRoute;
this is my mongoose comment schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Define collection and schema
let Comment = new Schema({
idUser: {
type : String
},
idParent : {
type : String
},
text : {
type : String
},
media : {
fieldname : { type : String },
originalname : { type : String },
encoding : { type : String },
mimetype : { type : String },
buffer : { type : Buffer },
},
creationDate : {
type : Date
}
},
{
collection: 'comments'
})
module.exports = mongoose.model('Comment', Comment);
this is my client side http get request
baseUrl = 'http://localhost:4000/api/comments';
headers = new HttpHeaders().set('Content-Type', 'application/json');
constructor(private http : HttpClient) { }
getCommentsByParentId(idParent : any){
return this.http.get(`${this.baseUrl}/read/byParentId/${idParent}`);
}
this is how I consume the api in the client side
getComments(){
this.commentService.getCommentsByParentId(this.idPost).subscribe({
next : (res : any) => {
this.comments = res
this.commentsCount = res.length
},
error : (err : any) => {
console.log("error getting comment list for post "+this.idPost)
}
})
}
client side error :
server side error :
thank you.
Edit :
post without the list of comments before I post a new comment
post after I post a new comment
Well, that's very obvious that the server can't find the entity in the DB.
You need to check one of the following:
Maybe when you restart the node server, you restart the db too. that can happen if you're using docker-compose locally. then when you run your node server again your DB starts but there's no data in the DB, therefore the service can't find any data.
After service restart you're using non-existing ID because of wrong UI flow.
I would guess that you're facing the first option.
I want to create A post route So I Can store the User Typed code snippets into the collection in MongoDB.
the schema would look like this:-
const newSnippetSchema= new mongoose.Schema({
title:String,
snippet:String
})
to be brief I am working on creating a web app like codeSandbox or code-pen where I can save the code user has saved or typed....
I want to send data in Json format when Post Route is triggered
Create a http post web api.
const express = require('express')
const mongoose = require('mongoose')
const NewSnippetSchema = require('./models/newSnippetSchema')
const app = express()
mongoose.connect('mongodb://localhost/mydb', {
useNewUrlParser: true, useUnifiedTopology: true
})
app.set('view engine', 'ejs')
app.use(express.urlencoded({ extended:false }))
app.post('/newSnippet', async (req, res) => {
await NewSnippetSchema.create({ title: req.body.title, snippet: req.body.snippet })
res.redirect('/')
})
app.listen(process.env.PORT || 5000);
catchAsync Code:
const catchAsync = (fn) =>
function asyncUtilWrap(...args) {
const fnReturn = fn(...args);
const next = args[args.length - 1];
return Promise.resolve(fnReturn).catch(next);
};
export default catchAsync;
AppError Code:
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
export default AppError;
Controller Code:
const snippetController = catchAsync(async (req, res, next) => {
const { title, snippet } = req.body;
if (!title || !snippt) {
return next(new AppError('All fields are required', 400));
}
const snippetDoc = await Snippet.create(req.body);
return res.status(201).json({
status: 'success',
snippetDoc
});
});
export default snippetController;
Router Code:
router.route('/snippet').post(snippetController);
The database has nothing to do with this. It doesn't matter how you store it, what matters is how render it in your HTML representation on the UI.
Just use the built-in HTML encoding function to encode the code snippet before storing it in the database.(eg - encode all & as & something like this).
After fetching the data, decode it back before rendering it on the UI.
This method runs at node server
const express = require("express");
const app = express();
const fs = require("fs");
const connectDb = require("./config/db");
const __init__ = (local = false) => {
fs.writeFile(
"./config/default.json",
`{
"mongoURI": ${
local
? `"mongodb://127.0.0.1:27017/test"`
: `"mongodb+srv://admin:<password>#abc-xxghh.mongodb.net/test?retryWrites=true&w=majority"`
}
}`,
function(err) {
if (err) {
return console.log(err);
}
connectDb();
}
);
};
__init__(true);
The problem is that if originally mongoURI: 127.0.0.1:27017, and if I do __init__(false), Node will try to connect to 127.0.0.1:27017, when it should be connecting to +srv uri.
If I run __init__(false) AGAIN, then it will connect to appropriate link.
Likewise, if I then run __init__(true), it will connect to srv+ when it should be connecting to local, and if I run __init__(true) again, only then it will connect to local.
What am I doing wrong here? I'm using the callback as Im supposed to, no?
Edit:
//config/db
// for mongoDB connection
const mongoose = require("mongoose");
// require the directory
const config = require("config");
// get all contents of JSON file
const db = config.get("mongoURI");
const connectDb = async () => {
try {
console.log("connecting to mongodb", db);
await mongoose.connect(db, {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false,
useUnifiedTopology: true
});
console.log("Mongo DB connected");
} catch (err) {
console.log("unable to connect to mongodb");
console.log(err.message);
//exit if failure
process.exit(1);
}
};
module.exports = connectDb;
I've even tried doing the following:
.....
console.log("Developing locally:", local);
// require the directory
const config = require("config");
// get all contents of JSON file
const db = config.get("mongoURI");
connectDb(db);
.....
But it still reads the old value
The problem is on execution order since the require is sync
The order now is:
const connectDb = require("./config/db");
const config = require("config");
const db = config.get("mongoURI"); // this has the OLD VALUE
fs.writeFile(...
await mongoose.connect(db, { // this is using the OLD REFERENCE
So you need to change your connectDb function like this:
const connectDb = async () => {
const config = require("config");
// get all contents of JSON file
const db = config.get("mongoURI");
try {
console.log("connecting to mongodb", db);
await mongoose.connect(db, {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false,
useUnifiedTopology: true
});
console.log("Mongo DB connected");
} catch (err) {
console.log("unable to connect to mongodb");
console.log(err.message);
//exit if failure
process.exit(1);
}
};
Anyway, I think this is not a nicer way to load config based on the environment, so I would suggest improving it using factory pattern.
Your code for URL local vs srv+ is correct. Problem i could see is placement of method connectDb();
fs.writeFile("fir arg - URL", "second -content", third - error fun {});
where in your code after function, connectDb() is placed after error fun. After it should be closed.
I'm testing an express server using super-test and I need to test a post call. I assume the post should be successful and return a status of 200 but it is returning 401. I've been told by someone that I need to pass a request body with the post but I'm unsure exactly how to do this.
I've attempted to use .send({name: 'aName'}) but that gives me the same 401 code.
Below is the app.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const hateoasLinker = require('express-hateoas-links');
const AValidator = require('./AValidator');
const BValidator = require('./BValidator');
const schema_v1 = require("./schema.json");
const {
logService: logger
} = require("#utils");
let aValidator = AValidator(schema_v1);
let ValidatorApi = BValidator.ValidatorApi('api');
let adminValidator = BValidator.ValidatorAdmin('admin');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(hateoasLinker);
app.post('/*/activate',admiValidator, (req, res) => {
console.log("In Activate===============>");
res.status(200);
res.json({
rel: "self",
method: "POST",
title: 'Activate Solution',
href: "/activate"
});
});
Here is the code for the BValidator
ValidatorAdmin = function(callType){
return function (req,res,next){
let authoizationHeader = req.headers['authorization'];
try {
Verifier.verifyPayload(authoizationHeader, callType, (verificationError) => {
if (verificationError) {
res.setHeader('Content-Type', 'application/json');
res.status(401);
res.json({
message : "verificationError "+verificationError.message
});
} else {
next();
}
});
} catch (authorizationError) {
res.setHeader('Content-Type', 'application/json');
res.status(401);
res.json({
message : authorizationError.message
});
}
}
}
Here is the app.test.js
const request = require('supertest');
const bodyParser = require('body-parser');
let AValidator = require('../src/AValidator');
let BValidator = require('../src/BValidator');
BValidator = jest.fn();
AValidator = jest.fn();
app = require('../src/app');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
describe('Test os GET/POST calls in app.js', ()=>{
test('Tests activate post', (done)=>{
BValidator.mockReturnValue({
ValidatorApi: (req,res,next)=>{
next();
},
ValidatorAdmin:(req,res,next)=>{
next();
}
});
AValidator.mockImplementation((schema)=>{
return function (req,res,next){
next();
}
});
request(app)
.post('/test/activate')
.set({name:'josh'})
.then((response)=>{
expect(response.statusCode).toBe(200);
done();
})
})
});
So ultimately I'd like this post to resolve successfully and return a status code of 200.
I am assuming you have set the parser correctly. Example:
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
Once this is set, I believe you're missing the headers. Example:
const payload = {name: 'john', email: 'xyz#sadfjak.com', password: '2342388' };
const res = await request(app)
.post('/api/register')
.send(payload)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
Your problem is that you have a wrong understanding of what those mock functions are doing. First of all, you are completely overwriting the original values of AValidator and BValidator with jest.fn().
So doing
let AValidator = require('../src/AValidator');
let BValidator = require('../src/BValidator');
in your test is redundant.
The purpose of mockReturnValue is so that you can call that function and get back the return value you specified.
Taken straight from Jest docs
const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call');
// 'first call', 'second call', 'default', 'default'
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
You are never using or calling your mock functions, and furthermore your api has no clue they even exist.
The solution is to provide the appropriate headers in the request when you run your tests so they don't fail in the middleware. Also, to do that, you have to know what Verifier.verifyPayload is doing.
with supertest your request should look like
request(app)
.post('/test/activate')
.set({authorization: 'a_valid_value_goes_here'})
.then((response)=>{
expect(response.statusCode).toBe(200);
done();
})
I have an iOS app which is sending a JSON packet to a webserver. The webserver code looks like this:
var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var app = express();
mongoose.connect('mongodb://localhost/test');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function (callback) {
console.log("MongoDB connection is open.");
});
// Mongoose Schema definition
var Schema = mongoose.Schema;
var LocationSchema = new Schema({
X: Number,
Y: Number,
Orientation: Number,
UserID: String,
Time: String
});
// Mongoose Model definition
var LocationsCollection = mongoose.model('locations', LocationSchema);
// create application/json parser
var jsonParser = bodyParser.json();
// URL management
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
app.post('/update', jsonParser, function (req, res) {
if (!req.body) return res.sendStatus(400);
else {
console.log(req.body);
}
});
// Start the server
var server = app.listen(3000, function () {
var host = server.address().address
var port = server.address().port
console.log('App listening at %s:%s',host, port)
});
The key part is the app.post method which processes the incoming http request being sent from my iOS app. At the moment, the method which prints the req.body to the console looks like this:
{
datapoint_1:
{ timestamp: '2015-02-06T13:02:40:361Z',
x: 0.6164286615466197,
y: -0.6234909703424794,
id: 'B296DF8B-6489-420A-97B4-6F0F48052758',
orientation: 271.3345946652066 },
datapoint_2:
{ timestamp: '2015-02-06T13:02:40:961Z',
x: 0.6164286615466197,
y: -0.6234909703424794,
id: 'B296DF8B-6489-420A-97B4-6F0F48052758',
orientation: 273.6719055175781 }
}
So, you can see the request is a nested JSON object. Ideally, I'd like to loop through the request objects (ie. the datapoints) and insert those into the mongoDB database (via mongoose). However, I can't seem to figure out how to do much of anything with the req.body. I can't seem to create a loop to iterate through the request or how to properly parse the nested JSON file so it matches the mongoose schema. Can anyone provide some guidance on how to insert these datapoints into the mongoose database?
Set body-parser's extended property to true to allow parsing nested objects.
var express = require('express');
var app = express()
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({
extended: true
}));
Answering my own question. But, after figuring out how to access the key/value pairs inside the nested JSON object... it became relatively easy to figure out the rest. The updated app.post function now looks like this:
app.post('/update', jsonParser, function (req, res) {
if (!req.body) return res.sendStatus(400);
else {
for(var datapoint in req.body){
//create new instance of LocationCollection document
var point = new LocationsCollection({
X:Number(req.body[datapoint]["x"]),
Y:Number(req.body[datapoint]["y"]),
Orientation:Number(req.body[datapoint]["orientation"]),
Time:req.body[datapoint]["timestamp"],
UserID:req.body[datapoint]["id"]
});
//insert the newly constructed document into the database
point.save(function(err, point){
if(err) return console.error(err);
else console.dir(point);
});
}
}
});
I can test if this worked by putting the following method inside the callback function once the mongodb connection is first established:
//Find all location points and print to the console.
console.log("Searching for all documents in Location Points Collection");
LocationsCollection.find(function(err,data){
if(err) console.error(err);
else console.dir(data);
});
This will print any documents that have been previously added to the database. Hopefully this helps.
Try somthing like this.
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json({limit:1024*1024, verify: function(req, res, buf){
try {
JSON.parse(buf);
} catch(e) {
res.send({
error: 'BROKEN_JSON'
});
}
}}));
It should be a simple for (var key in obj) loop:
app.post('/update', jsonParser, function (req, res) {
var locationObject = req.body(),
insertObjects = [],
key;
for (key in locationObject) { // loop through each object and insert them into our array of object to insert.
insertObjects.push(locationObject[key]);
}
if (!insertObjects.length) { // if we don't have any object to insert we still return a 200, we just don't insert anything.
return res.status(200).send({
success: true,
message: 'Nothing inserted, 0 locations in POST body',
count: 0;
});
}
LocationsCollection.create(insertObjects, function (err, res) {
if (err) {
return res.status(400).send({
success: false,
message: err.message
});
}
// we have successfully inserted our objects. let's tell the client.
res.status(200).send({
success: true,
message: 'successfully inserted locations',
count: insertObjects.length;
});
});
});
Mongo allows for inserting multiple documents with a single callback, which makes this a lot easier.
This also checks the schema to ensure only proper documents are created.