Our task is to create a trip cost calculator app.
Imagine going on a trip, and you have your app (which can be a progressive web app, or a mobile app for example) where you add any expense you make. Gasoline, hotels, food, tickets and so on.
When the trip ends, you archive it and it becomes part of the history - which you can navigate and see how much you spent in the past trips.
We’re not going to create the frontend of the application here, as this course is focused on Node.js and I don’t want to dilute the concepts.
What we’re going to build is the API for this project, using Express and MongoDB .
DEFINE WHAT WE NEED TO DO
In the introduction you saw the overview of what we’ll build. We need to manage a list of trips, and we need to track expenses for each of those trips.
Let’s now dissect this into details, and translate it into a series of API endpoints.
An endpoint is a unique URL we will call to create an operation.
Like adding a new trip with its name.
If you are unfamiliar with how HTTP works, take the time to read my tutorials on the HTTP protocol and how HTTP requests work.
At the beginning, there is no trip stored, and we need to add one. I imagine the app will ask the user for the name, and there will be a “Create trip” button. When clicked, the app will send the name to us, to the /trip
endpoint with the POST
HTTP method.
We have our first endpoint, which will accept a name
property.
POST /trip { name }
Another endpoint will list the trips, and it’s
GET /trips
By default, it will return the trips ordered by creation date.
When the user wants to add a new expense, the app will invoke the /expense
endpoint with the POST method, with some parameters that describe the expense
POST /expense { trip, date, amount, category, description }
trip
is the ID of the currently selected trip.
category
is the name of the category of the expense. We’ll provide a list of categories to choose from, which is static: travel
, food
, accomodation
, fun
.
When we want to retrieve a trip expenses, we call the /expenses
endpoint with the GET method:
GET /expenses { trip }
passing the trip
identifier.
LET’S START THE PROJECT
I am going to use a local installation of Node.js.
We start our Node.js project by going into a new folder (call it tripcost
) and typing the command npm init
.
Answer the questions (you can also just press enter to get the default values, or use npm init --yes
to skip them), and you are ready to go.
We’ll use MongoDB as our database.
If you are unfamiliar with MongoDB, check out my tutorials and install MongoDB on your system:
- The MongoDB basics tutorial
- How MongoDB is different from an SQL database
- How to use MongoDB with Node.js
Done? Great! Install the Node.js package right now with
npm install mongodb
While you’re here, also install Express:
npm install express
Create a server.js
file now, where we’ll store our API code, and start requiring Express and MongoDB:
const express = require('express')
const mongo = require('mongodb').MongoClient
Initialize the Express app:
const app = express()
And now we can add the stubs for the API endpoints we support:
app.post('/trip', (req, res) => { /* */ })
app.get('/trips', (req, res) => { /* */ })
app.post('/expense', (req, res) => { /* */ })
app.get('/expenses', (req, res) => { /* */ })
Finally, use the listen()
method on app
to start the server:
app.listen(3000, () => console.log('Server ready'))
Project up to now: https://glitch.com/edit/#!/node-course-project-tripcost-a?path=server.js
ADDING TRIPS
We offer the client a way to add trip using the POST /trip
endpoint:
app.post('/trip', (req, res) => { /* */ })
Let’s go ahead and implement it.
We already included the MongoDB library, so we can use it in our endpoint implementation.
const mongo = require('mongodb').MongoClient
Next, we build the MongoDB server URL. If you are running the project locally, and MongoDB locally too, the URL is likely this:
const url = 'mongodb://localhost:27017'
because 27017
is the default port.
Next, let’s connect to the database using connect()
:
let db
mongo.connect(
url,
(err, client) => {
if (err) {
console.error(err)
return
}
db = client.db('tripcost')
}
)
and while we’re here, let’s also get a reference to the trips and expenses collections:
let db, trips, expenses
mongo.connect(
url,
(err, client) => {
if (err) {
console.error(err)
return
}
db = client.db('tripcost')
trips = db.collection('trips')
expenses = db.collection('expenses')
}
)
Now we can go back to our endpoint.
This endpoint expects 1 parameter, name
, which represents how we call our trip. For example Sweden 2018 or Yosemite August 2018 .
See my tutorial on how to retrieve the POST query parameters using Express
We expect data coming as JSON, using the Content-Type: application/json
, so we need to use the express.json()
middleware:
app.use(express.json())
We can now access the data by referencing it from Request.body
:
app.post('/trip', (req, res) => {
const name = req.body.name
})
Once we have the name, we can use the trips.insertOne()
method to add the trip to the database:
app.post('/trip', (req, res) => {
const name = req.body.name
trips.insertOne({ name: name }, (err, result) => {
})
})
We handle the error, if present in the err
variable, otherwise we send a 200 response (successful) to the client, adding an ok: true
message in the JSON response:
app.post('/trip', (req, res) => {
const name = req.body.name
trips.insertOne({ name: name }, (err, result) => {
if (err) {
console.error(err)
res.status(500).json({ err: err })
return
}
console.log(result)
res.status(200).json({ ok: true })
})
})
That’s it! You can test this endpoint using the Insomnia application, a great way to test and interact with REST endpoints:
Project up to now https://glitch.com/edit/#!/node-course-project-tripcost-b?path=server.js
LIST THE TRIPS
The list of trips is returned by the GET /trips
endpoint. It accepts no parameters:
app.get('/trips', (req, res) => { /* */ })
In the previous lesson we initialized the trips
collection, so we can directly access that to get the list. We use the trips.find()
method, which result we must convert to an array using toArray()
:
app.get('/trips', (req, res) => {
trips.find().toArray((err, items) => {
})
})
Then we can handle the err
and items
results:
app.get('/trips', (req, res) => {
trips.find().toArray((err, items) => {
if (err) {
console.error(err)
res.status(500).json({ err: err })
return
}
res.status(200).json({ trips: items })
})
})
Here’s the result of the API call in Insomnia:
Project up to now: https://glitch.com/edit/#!/node-course-project-tripcost-c?path=server.js
ADD AN EXPENSE
In the previous lesson we got the list of trips. Every trip has an associated _id
property which is added by MongoDB directly when it’s added:
{
"trips": [
{
"_id": "5bdf03aed64fb0cd04e15728",
"name": "Yellowstone 2018"
},
{
"_id": "5bdf03c212d45cdb5ccec636",
"name": "Sweden 2017"
},
{
"_id": "5bdf047ccf4f42dc368590f6",
"name": "First trip"
}
]
}
We’ll use this _id
to register a new expense.
If you remember, the endpoint to add a new expense is this:
POST /expense { trip, date, amount, category, description }
trip
in this case will be the _id
of one of the trips we previously registered. Imagine that in the app, the user will add one trip, and that will remain the current trip until a new one is added (or selected).
Let’s go ahead and implement our stub:
app.post('/expense', (req, res) => { /* */ })
Like when adding a trip, we’re going to use the insertOne() method, this time on the expenses
collection.
We get 5 parameters from the request body:
trip
-
date
the date, in ISO 8601 format (e.g.2018-07-22T07:22:13
), in the GMT timezone -
amount
an integer with the amount -
category
which is one fromtravel
,food
,accomodation
,fun
-
description
a description for the expense, so we’ll remember about it later
app.post('/expense', (req, res) => {
expenses.insertOne(
{
trip: req.body.trip,
date: req.body.date,
amount: req.body.amount,
category: req.body.category,
description: req.body.description
},
(err, result) => {
if (err) {
console.error(err)
res.status(500).json({ err: err })
return
}
res.status(200).json({ ok: true })
}
)
})
The code at this point is available at https://glitch.com/edit/#!/node-course-project-tripcost-d?path=server.js
LIST ALL EXPENSES
The last piece of the puzzle is getting the expenses.
We need to fill the /expenses
endpoint stub, the last one missing:
app.get('/expenses', (req, res) => {
/* */
})
This endpoint accepts the trip
parameter, which like in the last lesson when we added an expense, it’s the _id
property of a trip stored in the database.
app.get('/expenses', (req, res) => {
expenses.find({trip: req.body.trip}).toArray((err, items) => {
if (err) {
console.error(err)
res.status(500).json({ err: err })
return
}
res.status(200).json({ trips: items })
})
})
This Insomnia screenshot shows it at work:
You can find the complete code available at https://glitch.com/edit/#!/node-course-project-tripcost-e?path=server.js
WRAPPING UP AND CHALLENGES
If you got the mentoring package, you can clone the project at https://github.com/flaviocopes/node-course-project-tripcost and implement the following coding challenges. Once you are done, email me and I will review your work (if you got the mentoring package!)
- Create a frontend using plain JavaScript, or any frontend framework you are familiar with, like Vue or React. Skip if you are not interested in the frontend side.
- The API now does not implement authentication. Read my tutorial on JWT authentication and implement authentication for the API, so you can have more than one user. Trips would be assigned to a specific user, so expenses are as well, automatically.
You can do those challenges later after you completed the other projects in the course, as it might take more time than you can currently dedicate (and some might also be too advanced right now) - also feel free to mail me for more guidance