Building an Ice Cream App: A Full-Stack Tutorial Using Express, Mongo/Mongoose, and React - Part 1/2
Welcome to this tutorial on building an ice cream app! If you're a fan of frozen desserts and are looking to expand your web development skills, you've come to the right place. In this tutorial, I'll be guiding you through the process of building a full-stack app that'll make your taste buds tingle. We'll be using Express to create a backend API, MongoDB as our database, and React for the frontend. You'll learn how to deploy the app using Render, a cloud platform that makes it easy to host and scale web apps. Let's get started!
In this build we will:
Setup for Express Build
npx create-react-app frontend
Your folder structure should look like this...
/express-react
-> /backend
-> /frontend
Initializing the Express app
Prior to proceeding, ensure that your terminal is opened on the backend folder to prevent any complications during the next steps.
npm init -y
dotenv: Loads environment variables from a .env file into the process.env object.
mongoose: A MongoDB object modeling tool that provides a schema-based solution to model application data.
cors: Enables Cross-Origin Resource Sharing (CORS) in a Node.js application.
morgan: Provides HTTP request logging middleware for Node.js applications.
npm install dotenv mongoose express cors morgan
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
touch .env .gitignore server.js
/node_modules
.env
We include the .env file and the /node_modules directory in the .gitignore file because they contain sensitive or unnecessary information that should not be committed to version control. The .env file contains sensitive configuration details, while the /node_modules directory contains dependencies that can be easily installed using npm. Excluding them from version control improves security and avoids unnecessary overhead.
DATABASE_URL=mongodb+src://...
PORT=4000
Starting Server.js
We'll start with the minimum requirements to launch server.js.
////////////////////////////////
// DEPENDENCIES
////////////////////////////////
// get .env variables
require("dotenv").config();
// pull PORT from .env, give default value of 3000
const { PORT = 3000 } = process.env;
// import express
const express = require("express");
// create application object
const app = express();
///////////////////////////////
// ROUTES
////////////////////////////////
// create a test route
app.get("/", (req, res) => {
res.send("I LOVE Ice Cream");
});
///////////////////////////////
// LISTENER
////////////////////////////////
app.listen(PORT, () => console.log(`listening on PORT ${PORT}`));
npm run dev
Adding a Database Connection
Let's update server.js to include a database connection! If you're new to Mongo/Mongoose, I recommend watching this great MongoDB/Mongoose Masterclass.
Recommended by LinkedIn
////////////////////////////////
// DEPENDENCIES
////////////////////////////////
// get .env variables
require("dotenv").config();
// pull PORT from .env, give default value of 3000
// pull MONGODB_URL from .env
const { PORT = 3000, DATABASE_URL } = process.env;
// import express
const express = require("express");
// create application object
const app = express();
// import mongoose
const mongoose = require("mongoose");
///////////////////////////////
// DATABASE CONNECTION
////////////////////////////////
// Establish Connection
mongoose.connect(DATABASE_URL, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
// Connection Events
mongoose.connection
.on("open", () => console.log("Your are connected to mongoose"))
.on("close", () => console.log("Your are disconnected from mongoose"))
.on("error", (error) => console.log(error));
///////////////////////////////
// ROUTES
////////////////////////////////
// create a test route
app.get("/", (req, res) => {
res.send("I LOVE Ice Cream");
});
///////////////////////////////
// LISTENER
////////////////////////////////
app.listen(PORT, () => console.log(`listening on PORT ${PORT}`));
Adding the Ice Creams Model
We should include an Ice Creams model in server.js and establish an index and a create route for managing Ice Creams. It's important to incorporate cors and express.json middleware as well.
//////////////////////////////
// DEPENDENCIES
////////////////////////////////
// get .env variables
require("dotenv").config();
// pull PORT from .env, give default value of 3000
// pull MONGODB_URL from .env
const { PORT = 3000, DATABASE_URL } = process.env;
// import express
const express = require("express");
// create application object
const app = express();
// import mongoose
const mongoose = require("mongoose");
// import middlware
const cors = require("cors");
const morgan = require("morgan");
///////////////////////////////
// DATABASE CONNECTION
////////////////////////////////
// Establish Connection
mongoose.connect(DATABASE_URL, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
// Connection Events
mongoose.connection
.on("open", () => console.log("Your are connected to mongoose"))
.on("close", () => console.log("Your are disconnected from mongoose"))
.on("error", (error) => console.log(error));
///////////////////////////////
// MODELS
////////////////////////////////
const IceCreamsSchema = new mongoose.Schema({
name: String,
image: String,
description: String,
});
const IceCreams = mongoose.model("IceCreams", IceCreamsSchema);
///////////////////////////////
// MiddleWare
////////////////////////////////
app.use(cors()); // to prevent cors errors, open access to all origins
app.use(morgan("dev")); // logging
app.use(express.json()); // parse json bodies
///////////////////////////////
// ROUTES
////////////////////////////////
// create a test route
app.get("/", (req, res) => {
res.send("I LOVE Ice Creams!");
});
// INDEX ROUTE
app.get("/icecreams", async (req, res) => {
try {
// send all people
res.json(await IceCreams.find({}));
} catch (error) {
//send error
res.status(400).json(error);
}
});
// CREATE ROUTE
app.post("/icecreams", async (req, res) => {
try {
// send all people
res.json(await IceCreams.create(req.body));
} catch (error) {
//send error
res.status(400).json(error);
}
});
///////////////////////////////
// LISTENER
////////////////////////////////
app.listen(PORT, () => console.log(`listening on PORT ${PORT}`));
/
Show, Update and Delete
Let's add an Update, Show and Delete API Route to server.js
////////////////////////////////
// DEPENDENCIES
////////////////////////////////
// get .env variables
require("dotenv").config();
// pull PORT from .env, give default value of 3000
// pull MONGODB_URL from .env
const { PORT = 3000, DATABASE_URL } = process.env;
// import express
const express = require("express");
// create application object
const app = express();
// import mongoose
const mongoose = require("mongoose");
// import middlware
const cors = require("cors");
const morgan = require("morgan");
///////////////////////////////
// DATABASE CONNECTION
////////////////////////////////
// Establish Connection
mongoose.connect(DATABASE_URL, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
// Connection Events
mongoose.connection
.on("open", () => console.log("Your are connected to mongoose"))
.on("close", () => console.log("Your are disconnected from mongoose"))
.on("error", (error) => console.log(error));
///////////////////////////////
// MODELS
////////////////////////////////
const IceCreamsSchema = new mongoose.Schema({
name: String,
image: String,
description: String,
});
const IceCreams = mongoose.model("IceCreams", IceCreamsSchema);
///////////////////////////////
// MiddleWare
////////////////////////////////
app.use(cors()); // to prevent cors errors, open access to all origins
app.use(morgan("dev")); // logging
app.use(express.json()); // parse json bodies
///////////////////////////////
// ROUTES
////////////////////////////////
// create a test route
app.get("/", (req, res) => {
res.send("I LOVE Ice Creams!");
});
// INDEX ROUTE
app.get("/icecreams", async (req, res) => {
try {
res.json(await IceCreams.find({}));
} catch (error) {
//send error
res.status(400).json(error);
}
});
// Update ROUTE
app.put("/icecreams/:id", async (req, res) => {
try {
res.json(
await IceCreams.findByIdAndUpdate(req.params.id, req.body, { new: true })
);
} catch (error) {
res.status(400).json(error);
}
});
// Delete ROUTE
app.delete("/icecreams/:id", async (req, res) => {
try {
res.json(await IceCreams.findByIdAndRemove(req.params.id));
} catch (error) {
res.status(400).json(error);
}
});
// CREATE ROUTE
app.post("/icecreams", async (req, res) => {
try {
res.json(await IceCreams.create(req.body));
} catch (error) {
//send error
res.status(400).json(error);
}
});
// SHOW ROUTE
app.get("/icecreams/:id", async (req, res) => {
try {
res.json(await IceCreams.findById(req.params.id));
} catch (error) {
res.status(400).json(error);
}
});
///////////////////////////////
// LISTENER
////////////////////////////////
app.listen(PORT, () => console.log(`listening on PORT ${PORT}`));
Deploy
git init
git add .
git commit -m "message"
git branch -M main
git remote add origin URL
git push -u origin main
Congratulations on successfully building and deploying an ice cream express API with MongoDB! Now, you can test your routes using Postman by simply replacing localhost with the deployed link from render. In part 2 of this tutorial, we will be building a full CRUD frontend for our ice cream app using React, connecting it to our API, and deploying it using Render.
Great work! This looks like an appetizing combination of ice cream and coding!