KEMBAR78
Building A Notes App With Express | PDF | Web Application | Databases
0% found this document useful (0 votes)
34 views23 pages

Building A Notes App With Express

This document provides a comprehensive guide to building a Notes App using Express.js, covering topics such as setting up the server, understanding routes and middleware, handling requests and responses, and connecting to a database. It includes hands-on exercises, example code, and explanations of key concepts like asynchronous programming and error handling. The document also outlines a project structure for the app and offers insights into best practices for backend development.

Uploaded by

tiolupopo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
34 views23 pages

Building A Notes App With Express

This document provides a comprehensive guide to building a Notes App using Express.js, covering topics such as setting up the server, understanding routes and middleware, handling requests and responses, and connecting to a database. It includes hands-on exercises, example code, and explanations of key concepts like asynchronous programming and error handling. The document also outlines a project structure for the app and offers insights into best practices for backend development.

Uploaded by

tiolupopo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 23

Building a Notes App with Express.

js
Week 1: Introduction to Express.js & Setting Up the
Server
Day 1: What is Express.js?

Objective:

 Understand what Express.js is and why it is widely used in backend


development.
 Learn how Express simplifies building web servers compared to raw
Node.js.

Key Topics:

1. What is Express.js?
o Express.js is a minimal and flexible Node.js framework that simplifies
backend development by providing built-in features for routing,
middleware, and handling HTTP requests and responses.
o Instead of manually handling server logic with Node.js' built-in HTTP
module, Express allows developers to create RESTful APIs and web
applications with much less boilerplate code.
o Express.js is a tool that helps us build websites and apps using
JavaScript.
o It makes it easier to handle web requests (like opening a webpage or
submitting a form).
o Without Express, we’d have to write a lot of extra code in Node.js.
2. Why Use Express?
o Efficiency: Reduces the complexity of handling server-side logic.
o Routing System: Helps define different paths (routes) for different
functionalities.
o Middleware Support: Allows processing requests before reaching
final logic.
o Integration: Works well with databases like MongoDB and
frameworks like React.js.
o Simplicity: Express makes backend development easier.
o Routing: We can create different pages or features quickly.
Works with Databases: Express helps us connect to databases like
o
MongoDB.
3. How Web Requests Work
o In technical terms, a client (browser) sends an HTTP request to a
server, which processes the request and sends back an HTTP
response.
o Express acts as the server, routing the request and determining how to
respond.
o Imagine a restaurant:
 You (the user) place an order (a request).
 The waiter (Express.js) takes your order.
 The kitchen (the backend) prepares the food and sends it back
(response).

Hands-on:

 Install Node.js and Express.


 Set up a simple Express server.
 Test the server using Postman or a browser.

Example Code (server.js):


const express = require('express');
const app = express();
const PORT = 5000;

// Basic GET route


app.get('/', (req, res) => {
res.send('Welcome to the Notes App API!');
});

// Start the server


app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Day 2: Understanding Routes and Middleware

Objective:

 Learn how to create different pages (routes) and handle GET, POST, PUT,
DELETE requests.
 Understand middleware and how it helps handle requests.
Key Topics:

1. What are Routes?


o In Express.js, routes define how the application responds to HTTP
requests at specific URLs.
o Routes can be defined for different HTTP methods like GET, POST,
PUT, and DELETE.
o Routes define how our app should respond to different user requests.
o Example: A user visits /about, and we show them an About Us page.
o A route method is derived from one of the HTTP methods and is
attached to an instance of the express class. Express supports methods
that correspond to HTTP methods: get, post, put and delete

// GET method route

app.get('/', (req, res) => {

res.send('GET request to the homepage');

});

// POST method route

app.post('/', (req, res) => {

res.send('POST request to the homepage');

});

 A route path is like an address that tells Express where to go when a


user visits a webpage or sends a request.

 If you type example.com/about, the app needs to know what to show.


A route path helps define that.

 Think of it as a doorway to different parts of your app.


o Route paths can be strings, string patterns, or regular expressions.

// This route path will match requests to /about

app.get('/about', (req, res) => {

res.send('About page');

});

// This route path will match requests to /random.text

app.get('/random.text', (req, res) => {

res.send('Random text');

});

2. What is Middleware?
o Middleware functions are functions that execute during the request-
response cycle.
o They can modify the request, response, or pass control to the next
middleware function.
o Middleware functions in Express.js are functions that have access to
the request object (req), the response object (res), and the next
middleware function in the application's request-response cycle. The
next middleware function is commonly denoted by a variable
named next.
o Middleware is like a security check at an airport.
o It processes requests before they reach their destination.
o Examples: Logging, checking user authentication, parsing data.

Types of Middleware

o Application-level middleware: Bound to an instance of express using


app.use() and executed for every request to the server.
o Router-level middleware: Bound to a specific route, executed only
when a request is made to that route
o Error-handling Middleware
o Error-handling middleware is defined with four arguments: (err, req,
res, next). This middleware must be defined after all other app.use()
and routes calls.
o Built-in Middleware
o Express has the following built-in middleware functions:
o express.static: Serves static assets such as HTML files, images,
etc.
o express.json: Parses incoming requests with JSON payloads.
Available with Express
o express.urlencoded: Parses incoming requests with URL-
encoded payloads.

// Middleware with no mount path; executed for every request to the


app

app.use((req, res, next) => {

console.log('Time:', Date.now());

next();

});

// Middleware mounted on /user route executed for any type of HTTP


request to /user

app.use('/user', function (req, res, next) {

console.log('Request Type:', req.method);

next();

});

//error handling
app.use(function (err, req, res, next) {

console.error(err.stack);

res.status(500).send('Something broke!');
});

Handling Requests and Responses

Handling requests and responses in Express.js is a fundamental aspect of


building web applications. This section delves into the core concepts and
provides practical examples to enhance your understanding.

Request Object (req)

The req object represents the HTTP request and contains properties for the
request query string, parameters, body, HTTP headers, and more. By
convention, it is referred to as req, but you can name it anything you like.

app.get('/user/:id', (req, res) => {

res.send(`User ID: ${req.params.id}`);

});

In the example above, req.params.id captures the id parameter from the


URL.

Response Object (res)

The res object represents the HTTP response that an Express app sends
when it gets an HTTP request. It is used to send back the desired HTTP
response.

app.get('/', (req, res) => {

res.send('Hello World');

});
You can also use res.json() to send a JSON response:

app.get('/data', (req, res) => {

res.json({ message: 'Hello World' });

});

Handling Different HTTP Methods

Express provides methods to handle different HTTP verbs such as GET,


POST, PUT, and DELETE.

GET Request

app.get('/', (req, res) => {

res.send('Got a GET request');

});

POST Request

app.post('/', (req, res) => {

res.send('Got a POST request');

});

PUT Request

app.put('/user', (req, res) => {

res.send('Got a PUT request at /user');

});

DELETE Request
app.delete('/user', (req, res) => {

res.send('Got a DELETE request at /user');

});

Error Handling in Express.js

Error handling is a critical aspect of building robust Express.js applications.


Proper error handling ensures that your application can gracefully handle
unexpected issues and provide meaningful feedback to users.

Basic Error Handling

In Express, error-handling middleware functions are defined in the same


way as other middleware functions, but with four arguments instead of three:
(err, req, res, next). For example:

app.use(function(err, req, res, next) {

console.error(err.stack);

res.status(500).send('Something broke!');

});

You should define error-handling middleware last, after other app.use() and
routes calls.

Catching Errors

Express catches errors that occur in synchronous code inside route handlers
and middleware automatically. For example:

app.get('/', (req, res) => {


throw new Error('BROKEN'); // Express will catch this on its own.

});

Hands-on:

 Create routes for a simple Notes API.


 Use middleware for logging and parsing data.

Example Code (notes.js):


const express = require('express');
const router = express.Router();
let notes = []; // Temporary storage

// GET all notes


router.get('/', (req, res) => {
res.json(notes);
});

// POST a new note


router.post('/', (req, res) => {
const note = req.body;
notes.push(note);
res.status(201).json(note); // 201 = Created
});

module.exports = router;

Adding Middleware (server.js):


app.use(express.json()); // Parses JSON data
app.use('/notes', require('./routes/notes'));

Week 2: Connecting to a Database & CRUD


Operations
Day 1: What is a Database?

Objective:

 Understand how databases store data.


 Learn how to connect our Notes App to a database.
Key Topics:

1. What is a Database?
o A database is a structured system for storing and managing data
efficiently.
o A database is like a digital notebook where we store and organize
data.
o It allows data to be retrieved, updated, and deleted using queries.
o Two main types:
 SQL Databases: Use tables and structured schemas, organized
like an Excel sheet (e.g., MySQL, PostgreSQL).
 NoSQL Databases: Store data flexibly, often tores data in a
format similar to JSON (e.g., MongoDB).
2. Why Use MongoDB?
o Stores data as JSON-like objects, making it easy to use with
JavaScript.
o Allows flexible and scalable storage.
o Works well with JavaScript.
o Stores data in an easy-to-read format.

Hands-on:

 Install MongoDB and Mongoose.


 Connect MongoDB to Express.

Example Code (server.js):


const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/notesDB', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('MongoDB Connected'))
.catch(err => console.error(err));

What Does “Asynchronous” Mean?


Imagine you're cooking. While you're waiting for the rice to boil, you don’t just sit there—you might cut
vegetables or prepare a sauce. This is like asynchronous programming: you don’t wait for one task to
finish before starting another.
In Express.js, which is a web framework for Node.js, asynchronous functions help us deal with things
that take time, like:

Reading/writing to a database

Making a network request

Reading files

If you waited for each one to finish before doing anything else, your app would be slow. That’s why we
use asynchronous functions.

---

Three Main Ways to Write Asynchronous Code in Express.js

1. Callback Functions (Old School Style)

A callback is a function you pass into another function to be called later.

app.get("/user", (req, res) => {


database.getUser((err, user) => {
if (err) return res.status(500).send("Error");
res.send(user);
});
});

Problems with Callbacks:

They can get messy with “callback hell” if you have many nested callbacks.

Harder to read and debug.

---

2. Promises

A Promise is like saying: “I promise I’ll give you the data later.” It’s an object that represents a value that
might not be available yet but will be in the future.

app.get("/user", (req, res) => {


database.getUser()
.then(user => res.send(user))
.catch(err => res.status(500).send("Error"));
});
Key Points:

.then() is called when the promise resolves (succeeds).

.catch() is called if there's an error.

---

3. Async/Await (Modern and Easy to Read)

This is just a prettier, more readable way to use promises. You write it like it's synchronous (normal
code), but it works asynchronously.

app.get("/user", async (req, res) => {


try {
const user = await database.getUser();
res.send(user);
} catch (err) {
res.status(500).send("Error");
}
});

Why Use async/await?

Looks like regular code

Easy to understand and debug

No callback hell

---

Error Handling in Async Functions

When using async/await, always use a try...catch block to catch errors.

Without it, unhandled errors could crash your app or go unnoticed.

---

Examples from Real-Life Express Apps

Example 1: Fetching a user from the database


app.get("/user/:id", async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).send("User not found");
res.send(user);
} catch (err) {
res.status(500).send("Something went wrong");
}
});

Example 2: Saving data to the database

app.post("/user", async (req, res) => {


try {
const user = new User(req.body);
await user.save();
res.status(201).send(user);
} catch (err) {
res.status(400).send("Failed to create user");
}
});

Below is an extended, comprehensive lesson note covering backend development with Express.js. You
can copy this text into a Word document (or your favorite text editor) and save it as a .docx file. This
document is designed to be thorough yet accessible for both teaching and your own understanding. It
covers Node.js basics, Express setup, project structure, routing, middleware, database integration with
Mongoose, JWT authentication, and connecting to the frontend—all based on our discussion. You can
expand or adjust as needed.

---

Comprehensive Lesson Notes for Backend Development with Express.js

Version: 1.0
Date: [Insert Date]

---

Table of Contents

1. Introduction to Node.js

2. What is Express.js?
3. Project Structure Overview

4. Setting Up the Server and Environment

5. Creating Models with Mongoose

User Model

Note Model

6. Building Routes and Controllers

Authentication Routes

Notes Routes (CRUD)

7. Middleware in Express.js

Built-In and Custom Middleware

JWT Authentication Middleware

8. Authentication with JSON Web Tokens (JWT)

9. Connecting the Frontend with the Backend

10. Deployment Considerations and Best Practices

11. Final Thoughts and Next Steps

---

1. Introduction to Node.js
Node.js is a JavaScript runtime built on Chrome’s V8 engine that allows developers to run JavaScript on
the server side. Its non-blocking, event-driven architecture makes it ideal for building scalable network
applications.

Key Points:

Non-blocking I/O: Node.js can handle many connections simultaneously.

npm: The Node Package Manager provides thousands of modules to add functionality.

JavaScript Everywhere: Developers can use JavaScript on both the client and server.

---

2. What is Express.js?

Express.js is a minimalist web framework for Node.js that simplifies creating server-side applications and
RESTful APIs. It abstracts many low-level details, enabling developers to focus on application logic.

Key Features:

Routing: Define endpoints (routes) for various HTTP methods.

Middleware: Easily plug in functions to handle requests/responses.

Rapid Development: Fast setup and simple API for building web applications.

---

3. Project Structure Overview

For our Notes App, we’ll organize the backend code as follows:

backend/
├── models/
│ ├── user.js // User schema (for authentication)
│ └── note.js // Note schema (for storing notes)
├── routes/
│ ├── auth.js // Authentication routes (signup, login)
│ └── notes.js // Notes routes (CRUD operations)
├── middleware/
│ └── auth.js // Middleware for JWT validation
├── server.js // Main server file
└── .env // Environment variables
This separation keeps our code modular and easier to maintain.

---

4. Setting Up the Server and Environment

Environment Setup

1. Initialize the Project:


Open a terminal in your project directory and run:

npm init -y

2. Install Dependencies:
Install Express, Mongoose, dotenv, bcryptjs, jsonwebtoken, cors, and body-parser:

npm install express mongoose dotenv bcryptjs jsonwebtoken cors body-parser


npm install --save-dev nodemon

3. Create a .env File:


In the root of the backend folder, create a .env file with:

PORT=5000
MONGO_URI=mongodb://localhost:27017/notesapp
JWT_SECRET=notesapp1234.com

(Note: For production, choose a more secure JWT secret.)

Server Setup (server.js)

Create server.js as the entry point of your application:

require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');

const authRoutes = require('./routes/auth');


const notesRoutes = require('./routes/notes');

const app = express();


const PORT = process.env.PORT || 5000;
// Middleware setup
app.use(cors());
app.use(bodyParser.json());
app.use(express.json());

// MongoDB connection
mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('Connected to MongoDB'))
.catch((err) => console.error('MongoDB connection error:', err));

// Set up routes
app.use('/api/auth', authRoutes);
app.use('/api/notes', notesRoutes);

// Global error handling middleware (optional)


app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: 'Internal Server Error' });
});

// Start the server


app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

---

5. Creating Models with Mongoose

User Model (models/user.js)

This schema defines the user structure for authentication.

const mongoose = require('mongoose');


const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({


name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
});

// Pre-save hook for hashing passwords


UserSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});

module.exports = mongoose.model('User', UserSchema);


Note Model (models/note.js)

This schema defines how notes are stored. Each note belongs to a user.

const mongoose = require('mongoose');

const NoteSchema = new mongoose.Schema({


user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
title: { type: String, required: true },
content: { type: String, required: true },
date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Note', NoteSchema);

---

6. Building Routes and Controllers

Authentication Routes (routes/auth.js)

These routes handle user signup and login.

const express = require('express');


const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
const router = express.Router();

// Signup route
router.post('/signup', async (req, res) => {
try {
const { name, email, password } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) return res.status(400).json({ message: 'User already exists' });

const user = new User({ name, email, password });


await user.save();
res.status(201).json({ message: 'User created successfully' });
} catch (err) {
res.status(500).json({ message: 'User creation failed', error: err.message });
}
});

// Login route
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) return res.status(404).json({ message: 'User not found' });

const isMatch = await bcrypt.compare(password, user.password);


if (!isMatch) return res.status(401).json({ message: 'Invalid credentials' });

const token = jwt.sign({ id: user._id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '1d' });
res.json({ token, username: user.name });
} catch (err) {
res.status(500).json({ message: 'Login failed', error: err.message });
}
});

module.exports = router;

Notes Routes (routes/notes.js)

These routes handle CRUD operations for notes. They’re protected by JWT authentication.

const express = require('express');


const Note = require('../models/note');
const auth = require('../middleware/auth');
const router = express.Router();

// Create Note
router.post('/', auth, async (req, res) => {
const { title, content, date } = req.body;
try {
const note = new Note({ title, content, date, user: req.user });
await note.save();
res.status(201).json(note);
} catch (error) {
res.status(500).json({ message: 'Error creating note', error: error.message });
}
});

// Get All Notes for the Authenticated User


router.get('/', auth, async (req, res) => {
try {
const notes = await Note.find({ user: req.user });
res.json(notes);
} catch (error) {
res.status(500).json({ message: 'Error fetching notes', error: error.message });
}
});
// Update Note
router.put('/:id', auth, async (req, res) => {
const { title, content, date } = req.body;
try {
const note = await Note.findOneAndUpdate(
{ _id: req.params.id, user: req.user },
{ title, content, date },
{ new: true }
);
if (!note) return res.status(404).json({ message: 'Note not found or unauthorized' });
res.json(note);
} catch (error) {
res.status(500).json({ message: 'Error updating note', error: error.message });
}
});

// Delete Note
router.delete('/:id', auth, async (req, res) => {
try {
const note = await Note.findOneAndDelete({ _id: req.params.id, user: req.user });
if (!note) return res.status(404).json({ message: 'Note not found or unauthorized' });
res.json({ message: 'Note deleted successfully' });
} catch (error) {
res.status(500).json({ message: 'Error deleting note', error: error.message });
}
});

module.exports = router;

---

7. Middleware in Express.js

Built-In Middleware:

express.json() – Parses JSON bodies.

cors() – Enables Cross-Origin Resource Sharing.

Custom Middleware: JWT Authentication

Create middleware/auth.js:

const jwt = require('jsonwebtoken');

const auth = (req, res, next) => {


const token = req.header('Authorization')?.split(' ')[1];
if (!token) return res.status(401).json({ message: 'Unauthorized' });

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded.id;
next();
} catch {
res.status(403).json({ message: 'Forbidden' });
}
};

module.exports = auth;

---

8. Authentication with JSON Web Tokens (JWT)

JWT Overview:

Purpose: Securely transmit information (e.g., user data) between client and server.

Process:

1. On login, the backend verifies credentials.

2. A JWT token is generated and sent to the client.

3. The client stores the token (e.g., in localStorage) and includes it in subsequent requests.

4. The backend verifies the token for protected routes.

Using JWT:

Login Route: Generates the token.

Auth Middleware: Validates the token in incoming requests.

Example (see auth.js route above).

---
9. Connecting the Frontend with the Backend

Frontend Considerations:

API Calls: Use fetch or similar methods to communicate with the backend.

Token Management: Store the JWT token in localStorage after login.

Protected Routes: Include the token in the Authorization header when making API requests.

Example (Frontend - app.js snippet):

// Login example
const loginForm = document.getElementById('loginForm');
if (loginForm) {
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value.trim();

try {
const response = await fetch('http://localhost:5000/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('jwt', data.token);
localStorage.setItem('username', data.username);
window.location.href = 'notes.html';
} else {
alert(data.message || 'Login failed.');
}
} catch (error) {
console.error('Login error:', error);
alert('An error occurred during login.');
}
});
}

Useful Commands:

Start the Server:


node server.js

or if using nodemon:

nodemon server.js

Install Dependencies:

npm install

Final Project: Build & Submit Your Notes App


 Add user roles (Admin/User).
 Secure API endpoints.
 Deploy the app.
 Submit a working GitHub link.

You might also like