In this project, we'll create a node back-end for DevMountain's swag shop. We'll make the back-end capable of registering users, logging in users, keeping track of guests, managing a shopping cart, and serving a pre-built React front-end. At the end of the project you'll be able to test your endpoints with Postman
and the pre-built front-end.
In this step, we'll install and require the npm packages we'll need.
- Run
npm install express express-session dotenv
. - Open
server/index.js
and require all the packages at the top of the file. - create a
.env
and include theSESSION_SECRET
andSERVER_PORT
- Both the tests and the front end are expecting the server port to be 3001.
server/index.js
require("dotenv").config();
const express = require("express");
const session = require("express-session");
.env
SERVER_PORT = 3001
SESSION_SECRET = s3cretsfjkdsfjdksljfkldskjld
In this step, we'll create an express app
, use express.json
and session
middleware, and have our server listen on port 3001
.
- Open
server/index.js
. - Create an express app.
- Create top-level middleware that use
express.json
andsession
.- Don't forget to pass in the configuration object into
session
. The object should have asecret
,resave
, andsaveUninitialized
parameter.
- Don't forget to pass in the configuration object into
Detailed Instructions
Now that index.js
has access to all the packages, let's create an express application. Create a variable called app
and set it equal to express
invoked. Destructure SERVER_PORT
and SESSION_SECRET
from process.env
.
require("dotenv").config();
const express = require("express");
const session = require("express-session");
const app = express();
let { SERVER_PORT, SESSION_SECRET } = process.env;
We can then add middleware to the app. Let's add express.json
so we can read JSON from the request body and add session
so we can create sessions. Remember that session
needs a configuration object as the first argument. The object should have a secret
, resave
, and saveUninitialized
property. secret
can be any string you like that is stored in your .env
file and resave
and saveUninitialized
should equal true
.
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true
})
);
Finally, let's have our app listen
on port 3001.
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
server/index.js
require("dotenv").config();
const express = require("express");
const session = require("express-session");
const app = express();
let { SERVER_PORT, SESSION_SECRET } = process.env;
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true
})
);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
In this step, we'll add custom middleware that will check to see if a session has been created. If a session hasn't been made yet, we'll create a user
object that keeps track of a user's username
, cart
, and total
.
- Create a folder called
middlewares
inserver/
. - Create a file called
checkForSession.js
inserver/middlewares/
. - Open
server/middlewares/checkForSession.js
. - Export a function that has a
req
,res
, andnext
parameter. - Check if the
req.session
has auser
object.- If the session doesn't have it, add it to the session.
- User object:
{ username: '', cart: [], total: 0 }
.
- User object:
- If the session does have it, call
next
.
- If the session doesn't have it, add it to the session.
Detailed Instructions
Let's begin by creating a folder called middlewares
in server/
. We'll keep all our middleware in this folder. Let's create a file called checkForSession.js
inside this folder. Export a function that captures req
, res
, and next
as parameters.
module.exports = function(req, res, next) {
};
In this middleware, we'll want to check to see if the sesssion has a user
object or not. The user
object will keep track of users on our website. We'll store what items are in their cart, the total cost of their cart, and their username. We'll only want to add the default user
object once. So let's add an if statement to check to see if the user
object doesn't exists.
module.exports = function(req, res, next) {
const { session } = req;
if ( !session.user ) {
}
};
If it doesn't exist, we'll want to add a user
object to the session. A user object should default to: { username: '', cart: [], total: 0 }
. We'll also want to call next
after the if statement so the request can reach the endpoint.
module.exports = function(req, res, next) {
const { session } = req;
if ( !session.user ) {
session.user = { username: '', cart: [], total: 0 };
}
next();
};
server/middlewares/checkForSession.js
module.exports = function(req, res, next) {
const { session } = req;
if (!session.user) {
session.user = { username: "", cart: [], total: 0 };
}
next();
};
In this step, we'll require our middleware in index.js
and add it to app
.
- Require
server/middlewares/checkForSession.js
. - Use
app.use
to addcheckForSession
.- NOTE: When we use our own simple middleware, we don't invoke the function inside of
app.use
. Pre-built middlewares likeexpress.json
are invoked because that is the way the author of the middleware designed it.
- NOTE: When we use our own simple middleware, we don't invoke the function inside of
server/index.js
require("dotenv").config();
const express = require("express");
const session = require("express-session");
const checkForSession = require("./middlewares/checkForSession");
const app = express();
let { SERVER_PORT, SESSION_SECRET } = process.env;
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true
})
);
app.use(checkForSession);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
In this step, we'll create a swag controller that can read
the swag from models/swag
and send it in a response. We'll also require it in server/index.js
and create an endpoint.
- Create a folder called
controllers
inserver/
. - Create a file called
swagController.js
inserver/controllers/
. - Open
server/controllers/swagController.js
. - Require
swag
fromserver/models/swag
.- This is just an array of swag objects.
- Export an object with a
read
method that has areq
,res
, andnext
parameter.- The
read
method should useres
to send a status of 200 with theswag
array.
- The
- Open
server/index.js
. - Require the swag controller.
- Create a
GET
endpoint at/api/swag
that calls theread
method from the swag controller. - Hit
http://localhost:3001/api/swag
inPostman
to make sure you are getting the swag array.
Detailed Instructions
Let's begin by creating a controllers
folder in server/
. This is where we'll store all our controller files. Let's create a file called swagController.js
in this folder. This controller will be responsible for sending the complete array of swag. Let's require the swag model from server/models/swag.js
. swag.js
is just a JavaScript file that exports an array of swag objects.
const swag = require('../models/swag');
Now that we have access to the swag array, let's use module.exports
to export an object with a read
method. This method should capture req
and res
as parameters. We'll then use res
to send a status of 200 and the entire swag
array.
const swag = require('../models/swag');
module.exports = {
read: ( req, res, next ) => {
res.status(200).send( swag );
}
};
Then, we can require it into server/index.js
and create an endpoint that calls this method. Open server/index.js
and require the swagController
.
const swagController = require('./controllers/swagController');
Now, let's make a GET
endpoint at /api/swag
that calls the read
method on our swagController
.
app.get("/api/swag", swagController.read);
server/controllers/swagController.js
const swag = require("../models/swag");
module.exports = {
read: (req, res, next) => {
res.status(200).send(swag);
}
};
server/index.js
require("dotenv").config();
const express = require("express");
const session = require("express-session");
const checkForSession = require("./middlewares/checkForSession");
const swagController = require("./controllers/swagController");
const app = express();
let { SERVER_PORT, SESSION_SECRET } = process.env;
// Middleware
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true
})
);
app.use(checkForSession);
// Endpoints
app.get("/api/swag", swagController.read);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
In this step, we'll create an authorization controller that can handle logging in, registering, signing out, and also reading the user from req.session
.
- Create an
authController.js
inserver/controllers/
. - Open
server/controllers/authController.js
. - At the top of the file require users from
models/users
.- This is where the users are kept after registering.
- A user object looks like:
{ id: integer, username: string, password: string }
- Underneath the require for
users
, add anid
variable that equals1
.- We'll increment this by one to make sure no users can have the same
id
.
- We'll increment this by one to make sure no users can have the same
- Export an object with a
login
,register
,signout
, andgetUser
method.- All methods should capture
req
andres
as parameters.
- All methods should capture
login
:- Should look on the request body for a
username
andpassword
. - Should check to see if a user from the
users
array matches that user/pass combination. - If the method finds a user:
- Update the value of
username
to the user's username on the request session's user object. - Return a status of 200 with the request session's user object.
- Update the value of
- If the method doesn't find a user:
- Return a status of 500.
- Should look on the request body for a
- For simplicity, we aren't going to do any verification on register. The
register
method should just push an object with anid
,username
, andpassword
to theusers
array. The method:- Should look on the request body for a
username
andpassword
. - Should push to the
users
array. - Should increment the value of the global
id
variable. - Should update the value of
username
on the request session's user object. - Should send a status of 200 with the request session's user object.
- Should look on the request body for a
signout
should destroy the session usingreq.session.destroy()
and then return thereq.session
object.- The return of this will be used for Unit Testing.
getUser
should simply send a status of 200 along with the request session's user object.
Detailed Instructions
Let's begin by creating an authController.js
file in server/controllers/
. This controller will be responsible for logging in users, registering users, signing out users, and also retrieving user information. Since we'll be working with users, we'll need to require the user.js
file from server/models/users.js
. This javascript file exports an array of all the users.
const users = require("../models/users");
We'll also need to create a global id
variable. We'll use this variable to assign id
s to newly registered users and then increment it by one so no users have the same id.
let id = 1;
Now, let's export an object with a login
, register
, signout
, and getUser
method. Each method should capture req
and res
as parameters.
module.exports = {
register: (req, res) => {
},
login: (req, res) => {
},
signout: (req, res) => {
},
getUser: (req, res) => {
}
}
Let's break down the file method by method. We'll begin with register
. We'll keep this method simple and won't check for any previous users with the same login information. This method should look for a username
and password
on the request body and then create a user object. It should use the global id
variable for the id
. After pushing the new user object to the users
array it should increment the value of id
by one so we can keep the value of id
unique. It should then set the value of username
on the request session's user object to the value of username
from the request body. Finally the method should return the updated user object with a status of 200.
register: (req, res) => {
const { session } = req;
const { username, password } = req.body;
users.push({ id, username, password });
id++;
session.user.username = username;
res.status(200).send(session.user);
}
Now let's focus on login
. This method should use username
and password
from the request body to find a user object in the users
array with the same user/pass combination. If it finds a user with that combination, it should update the value of username
on the request session's user object to value of username
from the request body. It should then send a status of 200 with the updated user object. If it doesn't find a user it should send a status of 500.
login: (req, res) => {
const { session } = req;
const { username, password } = req.body;
const user = users.find(user => user.username === username && user.password === password);
if (user) {
session.user.username = user.username;
res.status(200).send(session.user);
} else {
res.status(500).send('Unauthorized.');
}
}
Next up is signout
. This method is responsible for destroying the session and returning the session (which should be undefined at that point).
signout: (req, res) => {
req.session.destroy();
res.status(200).send(req.session);
}
Finally, let's focus on thegetUser
method. This method is responsible for reading the user object off of session and return it with a status of 200.
getUser: (req, res) => {
const { session } = req;
res.status(200).send(session.user);
}
server/controllers/authController.js
const users = require("../models/users");
let id = 1;
module.exports = {
register: (req, res) => {
const { session } = req;
const { username, password } = req.body;
users.push({ id, username, password });
id++;
session.user.username = username;
res.status(200).send(session.user);
},
login: (req, res) => {
const { session } = req;
const { username, password } = req.body;
const user = users.find(
user => user.username === username && user.password === password
);
if (user) {
session.user.username = user.username;
res.status(200).send(session.user);
} else {
res.status(500).send("Unauthorized.");
}
},
signout: (req, res) => {
req.session.destroy();
res.status(200).send(req.session);
},
getUser: (req, res) => {
const { session } = req;
res.status(200).send(session.user);
}
};
In this step, we'll require the auth controller in server/index.js
and create endpoints to hit every method on the controller.
- Open
server/index.js
. - Require the
authController.js
file. - Create the following endpoints: (
request method
,url
,controller method
)POST
-/api/login
-authController.login
.POST
-/api/register
-authController.register
.POST
-/api/signout
-authController.signout
.GET
-/api/user
-authController.getUser
.
- Test your endpoints using
Postman
.- Try registering a new user.
- Try logging in with that user.
- Try getting the session's information on the user ( /api/user ).
- Try signing out ( This should return nothing if the session was destroyed ).
server/index.js
require("dotenv").config();
const express = require("express");
const session = require("express-session");
const checkForSession = require("./middlewares/checkForSession");
const swagController = require("./controllers/swagController");
const authController = require('./controllers/authController');
const app = express();
let { SERVER_PORT, SESSION_SECRET } = process.env;
// Middleware
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true
})
);
app.use(checkForSession);
// Endpoints
//// Auth
app.post("/api/register", authController.register);
app.post("/api/login", authController.login);
app.post("/api/signout", authController.signout);
app.get("/api/user", authController.getUser);
//// Swag
app.get("/api/swag", swagController.read);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
In this step, we'll create a cart controller that can handle adding and deleting items from a user's cart. We'll also add a checkout method.
- Create a file called
cartController.js
inserver/controllers
. - Require
swag
frommodels/swag.js
.- This is just an array of swag objects.
- Export an object with an
add
,delete
, andcheckout
method. - Each method should capture
req
andres
as parameters. add
:- Should check the request parameter for an
id
. - Should use the
id
to see if it is already in the user's cart on session.- If it is, just send a status 200 with the request session's user object.
- If it isn't, find the swag object from
models/swag
using theid
and add it to the cart on session.- Add the price of the swag to the total on the session.
- Send a status 200 with the request session's user object.
- Should check the request parameter for an
delete
:- Should check the request parameter for an
id
. - Should use the
id
to remove the swag from cart and subtract it's price from the total. - Should send status 200 with the request session's user object.
- Should check the request parameter for an
checkout
:- Should set the cart back to an empty array on session.
- Should set the total back to 0 on session.
- Should send status 200 with the request session's user object.
Detailed Instructions
Let's begin by creating a cartController.js
file in server/controllers/
. This controller will be responsible for adding items to a user's cart and removing items from a user's cart. It will also handle the checkout process. This controller will need access to the swag
array so let's require swag.js
from server/models/swag.js
.
const swag = require("../models/swag");
Now, let's export an object with a add
, remove
, and checkout
method. Each method should capture req
, res
, and next
as parameters.
module.exports = {
add: (req, res) => {
},
delete: (req, res) => {
},
checkout: (req, res) => {
}
}
Let's break down the file, method by method. We'll start with add
. This method is responsible for making sure the swag isn't already in the cart. If it isn't, add it to the cart and increase the total by the price of the swag. If it is, just return the request session's user object with a status of 200. This method will use the request query to get an id
. We can then use this id
to see if it is already in the cart and preform the required logic.
add: (req, res) => {
const { id } = req.params;
let { user } = req.session;
// This will return -1 if it isn't in the cart
const index = swag.findIndex(swag => swag.id == id);
if (index !== -1) {
const selectedSwag = swag[index];
user.cart.push(selectedSwag);
user.total += selectedSwag.price;
}
res.status(200).send(user);
}
Now let's move on to delete
. This method will be responsible for removing swag from the cart. It should try and see if the swag is in the cart. If it is, remove the swag from the cart and subtract the price from the total. If it isn't, don't do anything to the cart. The method should then return a status of 200 with the request session user's object.
delete: (req, res) => {
const { id } = req.params;
const { user } = req.session;
const index = user.cart.findIndex(swag => swag.id == id);
const selectedSwag = swag.find(swag => swag.id == id);
if (index !== -1) {
user.cart.splice(index, 1);
user.total -= selectedSwag.price;
}
res.status(200).send(user);
}
Finally, let's create the checkout
method. This method will be responsible for resetting the value cart to an empty array and total to 0. The method should then send a status of 200 with the update request session' user object.
checkout: (req, res) => {
const { user } = req.session;
user.cart = [];
user.total = 0;
res.status(200).send(user);
}
server/controllers/cartController.js
const swag = require("../models/swag");
module.exports = {
add: (req, res) => {
const { id } = req.params;
let { user } = req.session;
// This will return -1 if it isn't in the cart
const index = swag.findIndex(swag => swag.id == id);
if (index !== -1) {
const selectedSwag = swag[index];
user.cart.push(selectedSwag);
user.total += selectedSwag.price;
}
res.status(200).send(user);
},
delete: (req, res) => {
const { id } = req.params;
const { user } = req.session;
const index = user.cart.findIndex(swag => swag.id == id);
const selectedSwag = swag.find(swag => swag.id == id);
if (index !== -1) {
user.cart.splice(index, 1);
user.total -= selectedSwag.price;
}
res.status(200).send(user);
},
checkout: (req, res) => {
const { user } = req.session;
user.cart = [];
user.total = 0;
res.status(200).send(user);
}
};
In this step, we'll require the cart controller and create endpoints to hit every method on the controller.
- Open
server/index.js
. - Require the cart controller.
- Create the following endpoints: (
request method
,url
,controller method
)POST
-/api/cart/checkout
-cartController.checkout
.- NOTE: It is very important that this endpoint come first.
POST
-/api/cart/:id
-cartController.add
.DELETE
-/api/cart/:id
-cartController.delete
.
- Test your endpoints using
Postman
.- Try adding an item to the cart by
id
( 1 - 35 ). - Try remove an item from the cart by
id
( 1 - 35 ). - Try checking out.
- Try adding an item to the cart by
server/index.js
require("dotenv").config();
const express = require("express");
const session = require("express-session");
const checkForSession = require("./middlewares/checkForSession");
const swagController = require("./controllers/swagController");
const authController = require("./controllers/authController");
const cartController = require("./controllers/cartController");
const app = express();
let { SERVER_PORT, SESSION_SECRET } = process.env;
// Middleware
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true
})
);
app.use(checkForSession);
// Endpoints
//// Auth
app.post("/api/register", authController.register);
app.post("/api/login", authController.login);
app.post("/api/signout", authController.signout);
app.get("/api/user", authController.getUser);
//// Swag
app.get("/api/swag", swagController.read);
//// Cart
app.post("/api/cart/checkout", cartController.checkout);
app.post("/api/cart/:id", cartController.add);
app.delete("/api/cart/:id", cartController.delete);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
In this step, we'll create a search controller that will also to filter swag by category
. The current categories are: hats
, shirts
, jackets
, sweaters
, pants
, and shorts
.
- Create a
searchController.js
inserver/controllers/
. - Open
server/controllers/searchController.js
. - Require
swag
frommodels/swag.js
.- This is just an array of swag objects.
- Export an object with a search method.
- This method should capture
req
andres
as parameters. - This method should check for a
category
from the request query. - This method should return a status 200 with the entire swag array if the category doesn't exist.
- This method should return a status 200 with the filtered array of swag by
category
if it does exist.
Detailed Instructions
Now for our last controller. Let's begin by creating a searchController.js
in server/controllers/
. This controller will need access to the swag model so let's require it from server/models/swag
. This controller will only need one method. Let's export an object that has a method called search. The method should capture req
and res
as parameters.
const swag = require('../models/swag');
module.exports = {
search: (req, res) => {
}
};
This method should look at the request query for a category
. If it can't find a category
, it should return a status of 200 with the entire swag array. If it can, it should filter the swag array by the category and return the filtered swag array.
search: (req, res) => {
const { category } = req.query;
if (!category) {
res.status(200).send(swag);
} else {
const filteredSwag = swag.filter(swag => swag.category === category);
res.status(200).send(filteredSwag);
}
}
server/controllers/searchController.js
const swag = require("../models/swag");
module.exports = {
search: (req, res) => {
const { category } = req.query;
if (!category) {
res.status(200).send(swag);
} else {
const filteredSwag = swag.filter(swag => swag.category === category);
res.status(200).send(filteredSwag);
}
}
};
In this step, we'll require the search controller and create an endpoint to hit the search method.
- Open
server/index.js
. - Require the search controller.
- Create a GET endpoint on
/api/search
that calls the search method on the search controller. - Test your endpoint using
Postman
.- Try getting a filtered swag list of
hats
. - Try getting a filtered swag list of
pants
.
- Try getting a filtered swag list of
server/index.js
require("dotenv").config();
const express = require("express");
const session = require("express-session");
const checkForSession = require("./middlewares/checkForSession");
const swagController = require("./controllers/swagController");
const authController = require("./controllers/authController");
const cartController = require("./controllers/cartController");
const searchController = require("./controllers/searchController");
const app = express();
let { SERVER_PORT, SESSION_SECRET } = process.env;
// Middleware
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true
})
);
app.use(checkForSession);
// Endpoints
//// Auth
app.post("/api/register", authController.register);
app.post("/api/login", authController.login);
app.post("/api/signout", authController.signout);
app.get("/api/user", authController.getUser);
//// Swag
app.get("/api/swag", swagController.read);
//// Cart
app.post("/api/cart/checkout", cartController.checkout);
app.post("/api/cart/:id", cartController.add);
app.delete("/api/cart/:id", cartController.delete);
// Search
app.get("/api/search", searchController.search);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
In this step, we'll use the provided postman_collection
to test all the endpoints and add express.static
to serve the pre-built front-end.
- Open
server/index.js
. - In the root of your project, run
npm run build
to create a freshbuild
folder - Add middleware to use
express.static
to serve up the build folder in/build
. - Open
http://localhost: /
to see the API interact with a React front-end. - Import the
postman_collection
intoPostman
and run the Unit Tests to make sure they all pass.
server/index.js
require("dotenv").config();
const express = require("express");
const session = require("express-session");
const checkForSession = require("./middlewares/checkForSession");
const swagController = require("./controllers/swagController");
const authController = require("./controllers/authController");
const cartController = require("./controllers/cartController");
const searchController = require("./controllers/searchController");
const app = express();
let { SERVER_PORT, SESSION_SECRET } = process.env;
// Middleware
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true
})
);
app.use(checkForSession);
app.use(express.static(`${__dirname}/../build`));
// Endpoints
//// Auth
app.post("/api/register", authController.register);
app.post("/api/login", authController.login);
app.post("/api/signout", authController.signout);
app.get("/api/user", authController.getUser);
//// Swag
app.get("/api/swag", swagController.read);
//// Cart
app.post("/api/cart/checkout", cartController.checkout);
app.post("/api/cart/:id", cartController.add);
app.delete("/api/cart/:id", cartController.delete);
// Search
app.get("/api/search", searchController.search);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
Express
If you see a problem or a typo, please fork, make the necessary changes, and create a pull request so we can review your changes and merge them into the master repo and branch.
© DevMountain LLC, 2017. Unauthorized use and/or duplication of this material without express and written permission from DevMountain, LLC is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to DevMountain with appropriate and specific direction to the original content.