Building an Ice Cream App: A Full-Stack Tutorial Using Express, Mongo/Mongoose, and React - Part 1/2
Even robots enjoy ice cream on the moon - a fun, AI-generated digital art 🤖🍦🌕🚀

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:

  • Build an Express API
  • Use Mongo/Mongoose with 1 model
  • Deploy the API with render
  • Build a Full Crud Frontend with React
  • Deploy with render

Setup for Express Build

  • Create a folder called express-react
  • Inside this folder create another folder called backend
  • Generate a React app called frontend 

npx create-react-app frontend        

Your folder structure should look like this...

/express-react
 -> /backend
 -> /frontend        

  • cd into backend folder

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.

  • Create a new node project by running the following command in your terminal:

npm init -y        

  • Install dependencies:

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        

  • In the package.json file, setup npm scripts. By defining these scripts in the package.json file, developers can quickly run these commands using the "npm run" command, followed by the script name. For example, "npm run start" or "npm run dev".

"scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
}        

  • Create the files .env, .gitignore, and server.js using the 'touch' command

 touch .env .gitignore server.js        

  • Update the .gitignore file with the following content.

/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.

  • Add the provided content to the .env file, replacing the placeholder with YOUR own MongoDB URI from mongodb.com.

DATABASE_URL=mongodb+src://...
PORT=4000        
No alt text provided for this image
No alt text provided for this image
No alt text provided for this image
Remember to substitute the placeholder "<password>" with your real password

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}`));        

  • Run the server and make sure you see "I LOVE Ice Cream" when you go to localhost:4000

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.

////////////////////////////////
// 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}`));        

  • Make sure you see the Mongoose Connection message when the server restarts

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}`));
/        

  • Let's have some fun and create three delicious ice cream flavors! We can use Postman to make POST requests to the /icecreams route and add our flavors to the database.

No alt text provided for this image

  • Let's give our taste buds a little adventure and test the /icecreams index route by sending a GET request. Are you ready to see what flavors we have in store?

No alt text provided for this image
No alt text provided for this image
Step into a whimsical world where ice cream sprouts from the earth in this stunning AI-generated digital art.

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}`));        

  • Fire up Postman one more time to test the new routes.

No alt text provided for this image
An example PUT request to update image link
No alt text provided for this image
Deleting Mint Chocolate Chip

Deploy

  • Before creating a Git repository in the backend folder, let's run git status to ensure that it is not already a Git repository. Once we confirm that it is not a repository, we can proceed to create a new Git repo in the backend folder.

No alt text provided for this image
This means we're good to go!

  • Create a new git repository

git init        

  • Add all files to staging

git add .        

  • Commit

git commit -m "message"        

  • Create a new repo on github.com (make sure its empty and public)
  • Rename the current branch in the local Git repository to "main"

git branch -M main        

  • Add the remote to your local repo, replace URL with your repos url

git remote add origin URL        

  • Push up your code 

git push -u origin main        

  • Create an account on render and create a new project.

No alt text provided for this image

  • Next, connect to git repository
  • Modify the start command to "yarn start" in the settings and proceed to deploy the application. However, if the deployment fails, do you know the reason why? It's because we haven't specified the necessary environment variables. Keep in mind that our repository doesn't have access to our .env variables, so we must redefine them during the deployment process on render. Set your DATABASE_URL env var.

No alt text provided for this image
We are live!

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.

Link to Project Repo

Great work! This looks like an appetizing combination of ice cream and coding!

To view or add a comment, sign in

More articles by Ayelet Hillel

Others also viewed

Explore content categories