node.js & express - global modules & best practices for application structure - javascript

I'm building a node.js app that is a REST api using express and mongoose for my mongodb. I've got the CRUD endpoints all setup now, but I was just wondering two things.
How do I expand on this way of routes, specifically, how do I share modules between routes. I want each of my routes to go in a new file, but obviously only one database connection as you can see i've included mongoose at the top of people.js.
Do I have to write out the schema of the model 3 times in my people.js? The first schema defines the model, then I list all the vars out in the createPerson and updatePerson functions. This feels like how I made php/mysql CRUD back in the day lol. For the update function, I've tried writing a loop to loop through "p" to auto detect what fields to update, but to no avail. Any tips or suggestions would be great.
Also, I'd love any opinions on the app as a whole, being new to node, it's hard to know that the way you are doing something is the most efficient or "best" practice. Thanks!
app.js
// Node Modules
var express = require('express');
app = express();
app.port = 3000;
// Routes
var people = require('./routes/people');
/*
var locations = require('./routes/locations');
var menus = require('./routes/menus');
var products = require('./routes/products');
*/
// Node Configure
app.configure(function(){
app.use(express.bodyParser());
app.use(app.router);
});
// Start the server on port 3000
app.listen(app.port);
/*********
ENDPOINTS
*********/
// People
app.get('/people', people.allPeople); // Return all people
app.post('/people', people.createPerson); // Create A Person
app.get('/people/:id', people.personById); // Return person by id
app.put('/people/:id', people.updatePerson); // Update a person by id
app.delete('/people/:id', people.deletePerson); // Delete a person by id
console.log('Server started on port ' + app.port);
people.js
//Database
var mongoose = require("mongoose");
mongoose.connect('mongodb://Shans-MacBook-Pro.local/lantern/');
// Schema
var Schema = mongoose.Schema;
var Person = new Schema({
first_name: String,
last_name: String,
address: {
unit: Number,
address: String,
zipcode: String,
city: String,
region: String,
country: String
},
image: String,
job_title: String,
created_at: { type: Date, default: Date.now },
active_until: { type: Date, default: null },
hourly_wage: Number,
store_id: Number, // Inheirit store info
employee_number: Number
});
var PersonModel = mongoose.model('Person', Person);
// Return all people
exports.allPeople = function(req, res){
return PersonModel.find(function (err, person) {
if (!err) {
return res.send(person);
} else {
return res.send(err);
}
});
}
// Create A Person
exports.createPerson = function(req, res){
var person = new PersonModel({
first_name: req.body.first_name,
last_name: req.body.last_name,
address: {
unit: req.body.address.unit,
address: req.body.address.address,
zipcode: req.body.address.zipcode,
city: req.body.address.city,
region: req.body.address.region,
country: req.body.address.country
},
image: req.body.image,
job_title: req.body.job_title,
hourly_wage: req.body.hourly_wage,
store_id: req.body.location,
employee_number: req.body.employee_number
});
person.save(function (err) {
if (!err) {
return res.send(person);
} else {
console.log(err);
return res.send(404, { error: "Person was not created." });
}
});
return res.send(person);
}
// Return person by id
exports.personById = function (req, res){
return PersonModel.findById(req.params.id, function (err, person) {
if (!err) {
return res.send(person);
} else {
console.log(err);
return res.send(404, { error: "That person doesn't exist." });
}
});
}
// Delete a person by id
exports.deletePerson = function (req, res){
return PersonModel.findById(req.params.id, function (err, person) {
return person.remove(function (err) {
if (!err) {
return res.send(person.id + " deleted");
} else {
console.log(err);
return res.send(404, { error: "Person was not deleted." });
}
});
});
}
// Update a person by id
exports.updatePerson = function(req, res){
return PersonModel.findById(req.params.id, function(err, p){
if(!p){
return res.send(err)
} else {
p.first_name = req.body.first_name;
p.last_name = req.body.last_name;
p.address.unit = req.body.address.unit;
p.address.address = req.body.address.address;
p.address.zipcode = req.body.address.zipcode;
p.address.city = req.body.address.city;
p.address.region = req.body.address.region;
p.address.country = req.body.address.country;
p.image = req.body.image;
p.job_title = req.body.job_title;
p.hourly_wage = req.body.hourly_wage;
p.store_id = req.body.location;
p.employee_number = req.body.employee_number;
p.save(function(err){
if(!err){
return res.send(p);
} else {
console.log(err);
return res.send(404, { error: "Person was not updated." });
}
});
}
});
}

I have taken another approach here. Not saying it is the best, but let me explain.
Each schema (and model) is in its own file (module)
Each group of routes for a particular REST resource are in their own file (module)
Each route module just requires the Mongoose model it needs (only 1)
The main file (application entry point) just requires all route modules to register them.
The Mongo connection is in the root file and is passed as parameter to whatever needs it.
I have two subfolders under my app root - routes and schemas.
The benefits of this approach are:
You only write the schema once.
You do not pollute your main app file with route registrations for 4-5 routes per REST resource (CRUD)
You only define the DB connection once
Here is how a particular schema file looks:
File: /schemas/theaterSchema.js
module.exports = function(db) {
return db.model('Theater', TheaterSchema());
}
function TheaterSchema () {
var Schema = require('mongoose').Schema;
return new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
address: { type: String, required: true },
latitude: { type: Number, required: false },
longitude: { type: Number, required: false },
phone: { type: String, required: false }
});
}
Here is how a collection of routes for a particular resource looks:
File: /routes/theaters.js
module.exports = function (app, options) {
var mongoose = options.mongoose;
var Schema = options.mongoose.Schema;
var db = options.db;
var TheaterModel = require('../schemas/theaterSchema')(db);
app.get('/api/theaters', function (req, res) {
var qSkip = req.query.skip;
var qTake = req.query.take;
var qSort = req.query.sort;
var qFilter = req.query.filter;
return TheaterModel.find().sort(qSort).skip(qSkip).limit(qTake)
.exec(function (err, theaters) {
// more code
});
});
app.post('/api/theaters', function (req, res) {
var theater;
theater.save(function (err) {
// more code
});
return res.send(theater);
});
app.get('/api/theaters/:id', function (req, res) {
return TheaterModel.findById(req.params.id, function (err, theater) {
// more code
});
});
app.put('/api/theaters/:id', function (req, res) {
return TheaterModel.findById(req.params.id, function (err, theater) {
// more code
});
});
app.delete('/api/theaters/:id', function (req, res) {
return TheaterModel.findById(req.params.id, function (err, theater) {
return theater.remove(function (err) {
// more code
});
});
});
};
And here is the root application file, which initialized the connection and registers all routes:
File: app.js
var application_root = __dirname,
express = require('express'),
path = require('path'),
mongoose = require('mongoose'),
http = require('http');
var app = express();
var dbProduction = mongoose.createConnection('mongodb://here_insert_the_mongo_connection_string');
app.configure(function () {
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(application_root, "public")));
app.use('/images/tmb', express.static(path.join(application_root, "images/tmb")));
app.use('/images/plays', express.static(path.join(application_root, "images/plays")));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.get('/api', function (req, res) {
res.send('API is running');
});
var theatersApi = require('./routes/theaters')(app, { 'mongoose': mongoose, 'db': dbProduction });
// more code
app.listen(4242);
Hope this was helpful.

I found this StackOverflow post very helpful:
File Structure of Mongoose & NodeJS Project
The trick is to put your schema into models directory. Then, in any route, you can require('../models').whatever.
Also, I generally start the mongoose db connection in app.js, and only start the Express server once the connection is up:
mongoose.connect('mongodb://localhost/whateverdb')
mongoose.connection.on('error', function(err) {
console.log("Error while connecting to MongoDB: " + err);
process.exit();
});
mongoose.connection.on('connected', function(err) {
console.log('mongoose is now connected');
// start app here
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
});

I'd take a look at this project https://github.com/madhums/node-express-mongoose-demo . It is a great example on how to build a nodejs application in a standard way.

Related

Node js & mongo API : Error: socket hang up

Im trying to make an API with node JS (express and jwt) with a mongodb.
I've make some routes (like to get markers for example), that's working fine.
BUT i've also make a route in order to post some markers. When I query it (that takes long time, and with Postman), and I've an error like :
"Could not get any response"
and in my console :
Error: socket hang up
My route controller :
const model = require('../models/markers');
module.exports = {
create: function(req, res, next) {
model.create({
param_1: req.body.param_1,
param_2: req.body.param_2,
param_3: req.body.param_3,
}, function (err, result) {
if (err)
next(err);
else
res.json({status: "success", message: "Marker added successfully", data: null});
});
},
getAll: function(req, res, next) {
let list = [];
model.find({}, function(err, items){
if (err){
next(err);
} else{
for (let item of items) {
list.push({
id: item._id,
param_1: item.param_1,
param_2 : item.param_2,
param_3: item.param_3
});
}
res.json({status:"success", message: "Markers list found", data:{markers: list}});
}
});
},
};
And for my route :
const express = require('express');
const router = express.Router();
const markerController = require('../controllers/markers');
router.get('/', markerController.getAll);
router.post('/', markerController.create);
module.exports = router;
My model :
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const MarkerSchema = new Schema({
param_1: {
type: String,
required: true
},
param_2: {
type: String,
},
param_3: {
type: [{
type: String,
enum: ['0.5', '1', ',1.25', '1.5', '2', '3+']
}],
default: ['pending']
}
});
module.exports = mongoose.model('Marker', MarkerSchema);
My get router works fine but my post route don't.
You should check #Will Alexander comment, if it's a copy paste you have a typo in:
param_2: req.bodyparam_2,

Node.js Express POST call not working although GET does

I am trying to write a REST backend using Node.js, express and MongoDB but am having some issues creating PUT calls for some reason. The issue is with app.post('/contacts/add', contacts.addContacts) as it works fine if I change it to GET but when I change it to either PUT or POST I get the error Cannot GET /contacts/add Any ideas?
I am using express 4.15.3, mongodb 3.4.5 and npm 4.2.0
server.js:
var express = require('express'),
contacts = require('./routes/contacts');
bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.get('/contacts/chalkboard/:id', contacts.getChalkboardContacts);
app.get('/contacts/get/:uid', contacts.getContacts);
app.post('/contacts/add', contacts.addContacts);
app.listen(3000);
console.log('Listening on port 3000...');
contacts.js
var mongo = require('mongodb');
mongo.BSONPure = require('bson').BSONPure;
var Server = mongo.Server,
Db = mongo.Db,
BSON = mongo.BSONPure;
var server = new Server('localhost', 27017, {auto_reconnect: true});
db = new Db('db', server);
db.open(function(err, db) {
if(!err) {
console.log("Connected to database");
db.collection('contacts', {strict:true}, function(err, collection) {
if (err) {
console.log("The 'contacts' collection doesn't exist. Creating it with sample data...");
populateDB();
}
});
}
});
exports.getChalkboardContacts = function(req, res) {
var uid = req.params.uid.toString();
var date = new Date();
var timeInMs = date.getMilliseconds();
console.log(uid);
db.collection('contacts', function(err, collection) {
console.log('collection: ' + collection);
collection.find({uid: uid, lastMessage: {$gte: timeInMs}}).toArray(function(err, items) {
res.send(items);
});
});
};
exports.getContacts = function(req, res) {
var uid = req.params.uid.toString();
console.log(uid);
db.collection('contacts', function(err, collection) {
console.log('collection: ' + collection);
collection.find({uid: uid}).toArray(function(err, items) {
res.send(items);
});
});
};
exports.addContacts = function(req, res) {
console.log('working');
db.collection('contacts', function(err, collection) {
var id = "592159bc3e48764418170399";
var contact = {uid: "592159bc3e48764418173333",
keyUid: "592159bc3e48764418171444",
name: "Billy Jean",
phoneNumber: "+491721894733",
battery: "47%", longitude: "0",
latitude: "0",
city: "city",
country: "country",
place: "place",
address: "address",
lastMessage: "lastMessage",
lastUpdated: "lastUpdated"};
collection.update({'uid':id}, {$push:{'contacts':contact}}, function(err, result) {
if (err) {
console.log('Error updating contact: ' + err);
res.send({'error':'An error has occurred'});
} else {
console.log('' + result + ' document(s) updated');
res.send(result);
}
});
});
};
I didn't spot anything immediately wrong with the code.
You might be falling into a very common pitfall when using body-parser for JSON. Your request must specify Content-Type: application/json in the request for the parser to parse it. Otherwise, it won't work.
If that doesn't do the trick, if you can share any error messages or response codes you get, it may illuminate another issue.

How to get data in Angular 2 service with Mlab

import { Injectable } from '#angular/core';
#Injectable()
export class UserService {
constructor() {
console.log("Users service initialized");
}
getUsers(){
//Mlab URL
//Example: mongodb://user:password#ds129038.mlab.com:29038/database_name
}
}
Hi, i'm looking the way to get users in angular 2 service from a database in MLAB. How could I receive them and iterate results?
Regards,
In your app folder make a new folder and name it "server"
Inside "server" folder make two new folders and name the "models", and "routes"
In models folder create a javascript file and name it same as your collection name in mlab/mongodb. Example "datacollection1.js"
Inside datacollection1.js paste the following code:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const datacollection1Schema = new Schema({
varone: String,
vartwo: String,
varthree: String,
varfour: String
});
module.exports = mongoose.model('datacollection1', herodataSchema, 'datacollection1class');
Inside "routes" folder create a file and name it "api.js"
Paste the following code in api.js
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const Datacollection1class = require('../models/datacollection1');
const db = "mongodb://:#ds155201.mlab.com:<12345>/";
mongoose.Promise = global.Promise;
mongoose.connect(db, function(err){
if(err) {
console.log("Error: " + err);
}
});
//Get all data
router.get('/datacollection1class', function(req, res) {
console.log("Get request for datacollection1class");
Datacollection1class.find({})
.exec(function(err, datacollection1class){
if(err) {
console.log("Error retrieving datacollection1class.");
} else {
res.json(datacollection1class);
}
})
});
//retrieve data by id
router.get('/datacollection1class/:id', function(req, res) {
console.log("Get request for sigle document");
Datacollection1class.findById(req.params.id)
.exec(function(err, datacollection1classsingle){
if(err) {
console.log("Error retrieving datacollection1classsingle.");
}
else
{
res.json(datacollection1classsingle);
}
})
});
//Add a single new documemt
router.post('/datacollection1classsingle', function(req, res){
console.log('Post a data set');
var newDatacollection1classsingle = new Datacollection1class();
newDatacollection1classsingle.varone = req.body.varone;
newDatacollection1classsingle.vartwo = req.body.vartwo;
newDatacollection1classsingle.varthree = req.body.varthree;
newDatacollection1classsingle.varfour = req.body.varfour;
newDatacollection1classsingle.save(function(err, insertedDatacollection1classsingle){
if (err) {
console.log('Error saving new hero data: '+err);
} else {
res.json(insertedDatacollection1classsingle);
}
});
});
//Update existing document
router.put('/hatacollection1class/:id', function(req, res) {
console.log('Update a video');
Datacollection1class.findByIdAndUpdate(req.params.id,
{
$set: {
varone: req.body.varone,
vartwo: req.body.vartwo,
varthree: req.body.varthree,
varfour: req.body.varfour
}
},
{
new:true
},
function(err, updatedDatacollection1classsingle) {
if(err){
res.send("Error updating Datacollection1class by id : "+err);
} else {
res.json(updatedDatacollection1classsingle);
}
});
});
// Delete a document
router.delete('/datacollection1class/:id', function(req, res) {
console.log('Deleting a datacollection1class');
Datacollection1class.findByIdAndRemove(req.params.id, function(err, deletedDatacollection1classsingle){
if (err) {
res.send("Error deleting Datacollection1class by id: "+err);
} else {
res.json(deletedDatacollection1classsingle);
}
});
});
Go to command prompt and using angular CLI create a class. Make sure Angular CLI is installed on your machine. Type in command prompt in your app folder
ng generate class datacollection1class
and hit enter. This will create a file named "datacollection1class.ts"
Inside datacollection1class.ts type the following code:
export class Datacollection1class {
_id: string;
varone: string;
vartwo: string;
varthree: string;
varfour: string;
}
You are all set. Now on your command prompt type:
ng build
Then on your command prompt type:
node server
Go to the browser and run "http://localhost:4200/api/datacollection1class" or if you have the id of the document, then "http://localhost:4200/api/datacollection1class/59305a02734d1d5068f2414e" or tey running it using postman
Feel free to ask questions. upvote if you liked the help.

NodeJS express - Cannot GET route

I am creating a basic API using express and mongoose. Now I want 2 GET URLS - one for getting all the entries from the database and another for getting a random entry. Now the route defined first - '/API/getWords' works fine, but when I navigate my browser to the second route - 'API/getRandomWord' I get the following error:
Cannot GET /API/getRandomWord
Now I can't figure out what I'm doing wrong. Can I only define one app.get, and now need to define the parameters that define different functions? Or is this the 'correct' way to do it?
Thanks for the help.
My api.js:
var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var Words = require('./models/words.js');
//initialize our express app
var app = express();
//use body parser with JSON
app.use(bodyParser.json());
//middleware for CORS requests
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
//addWords endpoint
app.post('/API/addWords', function(req, res) {
//get user from request body
var words = req.body;
var newWords = new Words({
quote: words.quote,
source: words.source,
author: words.author
});
newWords.save(function(err) {
if (err) {
if (err.code == 11000) {
res.status(400).send({
message: 'This quote already exists!'
});
} else {
res.status(500).send({
message: err.errmsg
});
}
} else {
console.log('quote saved!');
//if all goes well, send 200 + validation message
res.status(200).send({
message: 'Successfully quoted!'
});
}
});
});
//get all the words
app.get('/API/getWords', function(req, res) {
Words.find({}, function(err, words) {
if (err) {
res.status(500).send({
message: err.errmsg
});
} else {
res.status(200).send(words);
}
});
});
//get a random word
app.get('API/getRandomWord', function(req, res) {
//count ann the entries
Words.count().exec(function(err, count) {
//get a random number that is less or equal number of entries
var random = Math.floor(Math.random() * count);
//skip the random number, then return 1 entry
Words.findOne().skip(random).exec(
function(err, words) {
if (err) {
res.status(500).send({
message: err.errmsg
});
} else {
res.status(200).send(words);
}
});
});
});
//connect to mongoDB
mongoose.connect('');
//define our server
var server = app.listen(3000, function() {
console.log('api listening on ', server.address().port);
});
my model (words.js):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// create a schema
var wordsSchema = new Schema({
author: {
type: String,
default: 'unknown',
index: true
},
source: {
type: String,
default: 'unknown',
index: true
},
quote: {
type: String,
unique: true,
required: true
},
created: Date,
updated: Date
});
//create the date
wordsSchema.pre('save', function(next) {
//get current date
var currentDate = new Date();
//assign date
this.updated = currentDate;
if (!this.created) {
this.created = currentDate;
}
next();
});
var Words = mongoose.model('Words', wordsSchema);
// make this available to our users in our Node applications
module.exports = Words;
Your route for /API/getRandomWord is missing the leading /, so it needs to be:
app.get('/API/getRandomWord', function(req, res) {
instead of:
app.get('API/getRandomWord', function(req, res) {

Building an api with Express and Waterline ORM

I'm trying to build an API with Express and Waterline ORM. The adapter I'm using is mongodb-adapter. The reason I'm trying to do this from outside Sails is that I want better understand the Waterline ORM so I can contribute to the Sails Couch Adapter. Here's what I got.
var express = require('express'),
app = express(),
bodyParser = require('body-parser'),
methodOverride = require('method-override'),
Waterline = require('waterline');
var mongoAdapter = require('sails-mongo');
app.use(bodyParser());
app.use(methodOverride());
mongoAdapter.host = 'localhost';
mongoAdapter.schema = true;
mongoAdapter.database = 'waterline-mongo';
app.models = {};
var User = Waterline.Collection.extend({
adapter:'mongodb',
// identity: 'user',
attributes: {
first_name: 'string',
last_name: 'string'
}
});
app.post('/users', function(req, res) {
console.log(req.body);
app.models.user.create(req.body, function(err, model) {
if(err) return res.json({ err: err }, 500);
res.json(model);
});
});
new User({ adapters: { mongodb: mongoAdapter }}, function(err, collection) {
app.models.user = collection;
// Start Server
app.listen(3000);
console.log('Listening on 3000');
});
So from what I understand collection will have the create/update/destory methods as defined by the Waterline API. However when I post to /users, I get a 'cannot call method "create" of undefined. The version of Waterline I'm using is 0.9.16. I'm probably setting this up wrong. Thanks in advance.
You'll have to add these instructions:
var orm = new Waterline();
var config = {
// Setup Adapters
// Creates named adapters that have have been required
adapters: {
'default': 'mongo',
mongo: require('sails-mongo')
},
// Build Connections Config
// Setup connections using the named adapter configs
connections: {
'default': {
adapter: 'mongo',
url: 'mongodb://localhost:27017/unparse'
}
}
};
var User = Waterline.Collection.extend({
identity: 'user',
connection: 'default',
attributes: {
first_name: 'string',
last_name: 'string'
}
});
orm.loadCollection(User);
orm.initialize(config, function(err, data) {
if (err) {
throw err;
}
app.models = data.collections;
app.connections = data.connections;
app.listen(3000);
});

Categories

Resources