Node & Express¶
nodemon | Automatically restarts node application when file changes |
Usage¶
- Start up server
- Go to
localhost:5000/api/v1/restaurants
File Tree¶
- backend
- api
- restaurants.controller.js
- restaurants.route.js
- reviews.controller.js
- dao (Data Access Object)
- restaurantsDAO.js
- reviewsDAO.js
- node_modules
- .env
- index.js
- package-lock.json
- package.json
- server.js
package.json
¶
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module", // allows use to import statements from es6
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"bson": "^4.2.2",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"mongodb": "^3.6.4"
}
}
server.js
¶
import express from "express"
import cors from "cors"
import restaurants from "./api/restaurants.route.js"
const app = express()
app.use(cors())
app.use(express.json()) // allow server to accept & read json in requests
// default route
app.use(
"/api/v1/restaurants",
restaurants
)
// fallback route
app.use(
"*",
(req, res) => res.status(404).json({ error: "not found"})
)
// export the app as a module,
// so that it can be imported in another file that accesses the database
export default app
.env
¶
Set the uri of the database
index.js
¶
import app from "./server.js"
import mongodb from "mongodb"
import dotenv from "dotenv"
import RestaurantsDAO from "./dao/restaurantsDAO.js"
import ReviewsDAO from "./dao/reviewsDAO.js"
dotenv.config()
const MongoClient = mongodb.MongoClient
const port = process.env.PORT || 8000
MongoClient.connect(
process.env.RESTREVIEWS_DB_URI, // connect to database
{
poolSize: 50,
wtimeout: 2500,
useNewUrlParse: true }
)
.catch(err => { // check for error
console.error(err.stack)
process.exit(1)
})
.then(async client => { // start webserver
await RestaurantsDAO.injectDB(client)
await ReviewsDAO.injectDB(client)
app.listen(port, () => {
console.log(`listening on port ${port}`)
})
})
)
restaurants.controller.js
¶
import RestaurantsDAO from "../dao/restaurantsDAO.js"
export default class RestaurantsController {
static async apiGetRestaurants(req, res, next) {
const restaurantsPerPage = req.query.restaurantsPerPage ? parseInt(req.query.restaurantsPerPage, 10) : 20
const page = req.query.page ? parseInt(req.query.page, 10) : 0
let filters = {}
if (req.query.cuisine) {
filters.cuisine = req.query.cuisine
} else if (req.query.zipcode) {
filters.zipcode = req.query.zipcode
} else if (req.query.name) {
filters.name = req.query.name
}
const { restaurantsList, totalNumRestaurants } = await RestaurantsDAO.getRestaurants({
filters,
page,
restaurantsPerPage,
})
let response = {
restaurants: restaurantsList,
page: page,
filters: filters,
entries_per_page: restaurantsPerPage,
total_results: totalNumRestaurants,
}
res.json(response)
}
static async apiGetRestaurantById(req, res, next) {
try {
let id = req.params.id || {}
let restaurant = await RestaurantsDAO.getRestaurantByID(id)
if (!restaurant) {
res.status(404).json({ error: "Not found" })
return
}
res.json(restaurant)
} catch (e) {
console.log(`api, ${e}`)
res.status(500).json({ error: e })
}
}
static async apiGetRestaurantCuisines(req, res, next) {
try {
let cuisines = await RestaurantsDAO.getCuisines()
res.json(cuisines)
} catch (e) {
console.log(`api, ${e}`)
res.status(500).json({ error: e })
}
}
}
restaurants.route.js
¶
import express from "express"
import RestaurantsCtrl from "./restaurants.controller.js"
import ReviewsCtrl from "./reviews.controller.js"
const router = express.Router()
router.route("/").get(RestaurantsCtrl.apiGetRestaurants)
router.route("/id/:id").get(RestaurantsCtrl.apiGetRestaurantById)
router.route("/cuisines").get(RestaurantsCtrl.apiGetRestaurantCuisines)
router
.route("/review")
.post(ReviewsCtrl.apiPostReview)
.put(ReviewsCtrl.apiUpdateReview)
.delete(ReviewsCtrl.apiDeleteReview)
export default router
reviews.controller.js
¶
import ReviewsDAO from "../dao/reviewsDAO.js"
export default class ReviewsController {
static async apiPostReview(req, res, next) {
try {
const restaurantId = req.body.restaurant_id
const review = req.body.text
const userInfo = {
name: req.body.name,
_id: req.body.user_id
}
const date = new Date()
const ReviewResponse = await ReviewsDAO.addReview(
restaurantId,
userInfo,
review,
date,
)
res.json({ status: "success" })
} catch (e) {
res.status(500).json({ error: e.message })
}
}
static async apiUpdateReview(req, res, next) {
try {
const reviewId = req.body.review_id
const text = req.body.text
const date = new Date()
const reviewResponse = await ReviewsDAO.updateReview(
reviewId,
req.body.user_id,
text,
date,
)
var { error } = reviewResponse
if (error) {
res.status(400).json({ error })
}
if (reviewResponse.modifiedCount === 0) {
throw new Error(
"unable to update review - user may not be original poster",
)
}
res.json({ status: "success" })
} catch (e) {
res.status(500).json({ error: e.message })
}
}
static async apiDeleteReview(req, res, next) {
try {
const reviewId = req.query.id
const userId = req.body.user_id
console.log(reviewId)
const reviewResponse = await ReviewsDAO.deleteReview(
reviewId,
userId,
)
res.json({ status: "success" })
} catch (e) {
res.status(500).json({ error: e.message })
}
}
}
restaurantsDAO.js
¶
import mongodb from "mongodb"
const ObjectId = mongodb.ObjectID
let restaurants
export default class RestaurantsDAO {
static async injectDB(conn) {
if (restaurants) {
return
}
try {
restaurants = await conn.db(process.env.RESTREVIEWS_NS).collection("restaurants")
} catch (e) {
console.error(
`Unable to establish a collection handle in restaurantsDAO: ${e}`,
)
}
}
static async getRestaurants({
filters = null,
page = 0,
restaurantsPerPage = 20,
} = {}) {
let query
if (filters) {
if ("name" in filters) {
query = { $text: { $search: filters["name"] } } // $text needs to be configured in mongodb
} else if ("cuisine" in filters) {
query = { "cuisine": { $eq: filters["cuisine"] } }
} else if ("zipcode" in filters) {
query = { "address.zipcode": { $eq: filters["zipcode"] } }
}
}
let cursor
try {
cursor = await restaurants
.find(query)
} catch (e) {
console.error(`Unable to issue find command, ${e}`)
return { restaurantsList: [], totalNumRestaurants: 0 }
}
const displayCursor = cursor.limit(restaurantsPerPage).skip(restaurantsPerPage * page)
try {
const restaurantsList = await displayCursor.toArray()
const totalNumRestaurants = await restaurants.countDocuments(query)
return { restaurantsList, totalNumRestaurants }
} catch (e) {
console.error(
`Unable to convert cursor to array or problem counting documents, ${e}`,
)
return { restaurantsList: [], totalNumRestaurants: 0 }
}
}
static async getRestaurantByID(id) {
try {
const pipeline = [
{
$match: {
_id: new ObjectId(id),
},
},
{
$lookup: {
from: "reviews",
let: {
id: "$_id",
},
pipeline: [
{
$match: {
$expr: {
$eq: ["$restaurant_id", "$$id"],
},
},
},
{
$sort: {
date: -1,
},
},
],
as: "reviews",
},
},
{
$addFields: {
reviews: "$reviews",
},
},
]
return await restaurants.aggregate(pipeline).next()
} catch (e) {
console.error(`Something went wrong in getRestaurantByID: ${e}`)
throw e
}
}
static async getCuisines() {
let cuisines = []
try {
cuisines = await restaurants.distinct("cuisine")
return cuisines
} catch (e) {
console.error(`Unable to get cuisines, ${e}`)
return cuisines
}
}
}
reviewsDAO.js
¶
import mongodb from "mongodb"
const ObjectId = mongodb.ObjectID
let reviews
export default class ReviewsDAO {
static async injectDB(conn) {
if (reviews) {
return
}
try {
reviews = await conn.db(process.env.RESTREVIEWS_NS).collection("reviews")
} catch (e) {
console.error(`Unable to establish collection handles in userDAO: ${e}`)
}
}
static async addReview(restaurantId, user, review, date) {
try {
const reviewDoc = { name: user.name,
user_id: user._id,
date: date,
text: review,
restaurant_id: ObjectId(restaurantId), }
return await reviews.insertOne(reviewDoc)
} catch (e) {
console.error(`Unable to post review: ${e}`)
return { error: e }
}
}
static async updateReview(reviewId, userId, text, date) {
try {
const updateResponse = await reviews.updateOne(
{ user_id: userId, _id: ObjectId(reviewId)},
{ $set: { text: text, date: date } },
)
return updateResponse
} catch (e) {
console.error(`Unable to update review: ${e}`)
return { error: e }
}
}
static async deleteReview(reviewId, userId) {
try {
const deleteResponse = await reviews.deleteOne({
_id: ObjectId(reviewId),
user_id: userId,
})
return deleteResponse
} catch (e) {
console.error(`Unable to delete review: ${e}`)
return { error: e }
}
}
}
References¶
- Web Dev Simplified | Your First Node.js Web Server
- Wed Dev Simplified | Learn Express JS In 35 Minutes
- Web Dev Simplified | Build A REST API With Node.js, Express, & MongoDB - Quick