Great job so far! In this course, we will put together everything we have learned to create one full-stack application. To start off, we'll write the back-end using an Express server.
In this lesson, you will learn how to set up an Express server from scratch and connect it to a MongoDB database. We'll cover middleware setup, database connection, schema and model creation, and CRUD operations. By the end of this lesson, you'll have a solid foundation for building and managing your server-side logic for our Todo List application.
Let's import the necessary modules and configure our Express server.
JavaScript1const express = require('express'); 2const cors = require('cors'); 3 4const app = express(); 5 6app.use(express.json()); 7app.use(cors({ origin: 'http://localhost:3000' }));
As you might recall, the express.json() middleware parses incoming request bodies as JSON, making the content available under the req.body
property. The cors middleware enables Cross-Origin Resource Sharing, allowing our server to accept requests from the specified origin, in this case, http://localhost:3000.
Next, we'll connect our Express server to a MongoDB database using Mongoose, which provides a schema-based solution to model our application data. To configure Mongoose, import and set it up to connect to a local MongoDB instance.
JavaScript1const mongoose = require('mongoose'); 2 3mongoose.connect('mongodb://127.0.0.1:27017/todoapp', { useNewUrlParser: true, useUnifiedTopology: true }) 4 .then(() => console.log('MongoDB connected...')) 5 .catch(err => console.error('MongoDB connection error:', err));
Running this code will attempt to connect to a MongoDB instance running locally on port 27017 in a database named todoapp
. If successful, it will log "MongoDB connected...".
Now that we have our database connection set up, let's define a schema and model for our Todo items. A Mongoose schema defines the structure of the documents within a collection.
JavaScript1const TodoSchema = new mongoose.Schema({ 2 text: { type: String, required: true }, 3 completed: { type: Boolean, default: false } 4});
The next step is to create a Mongoose model, which is a compiled version of the schema. This model allows us to create, read, update, and delete documents in a collection.
JavaScript1const Todo = mongoose.model('Todo', TodoSchema);
With this, our backend now knows how to structure our Todo items in MongoDB.
Before diving into the a new operation, let's review the basic CRUD operations: POST, GET, PUT, and DELETE.
-
POST is used to create a new resource. For example, to add a new Todo, you can use:
JavaScript1app.post('/todos', async (req, res) => { 2 try { 3 const newTodo = new Todo({ 4 text: req.body.text, 5 completed: req.body.completed 6 }); 7 const savedTodo = await newTodo.save(); 8 res.status(201).json(savedTodo); 9 } catch (err) { 10 res.status(400).json({ error: err.message }); 11 } 12});
-
GET is used to retrieve resources. For example, to get all Todos, you can use:
JavaScript1app.get('/todos', async (req, res) => { 2 try { 3 const todos = await Todo.find(); 4 res.status(200).json(todos); 5 } catch (err) { 6 res.status(500).json({ error: err.message }); 7 } 8});
-
PUT is used to update an existing resource. For example, to update a specific Todo by its ID:
JavaScript1app.put('/todos/:id', async (req, res) => { 2 try { 3 const updatedTodo = await Todo.findByIdAndUpdate(req.params.id, req.body, { new: true }); 4 res.status(200).json(updatedTodo); 5 } catch (err) { 6 res.status(400).json({ error: err.message }); 7 } 8});
-
DELETE is used to remove a resource. For example, to delete a specific Todo by its ID:
JavaScript1app.delete('/todos/:id', async (req, res) => { 2 try { 3 await Todo.findByIdAndDelete(req.params.id); 4 res.status(204).end(); 5 } catch (err) { 6 res.status{new: true}500).json({ error: err.message }); 7 } 8});
The PATCH operation is used to update partial details about an existing resource without replacing the entire entry. Note that this differs from the PUT operation, which replaces the entire resource with the updated content. This is particularly useful when you only want to update one field of a resource.
To implement PATCH for a Todo item, we'll use the following:
JavaScript1app.patch('/api/todos/:id', async (req, res) => { 2 try { 3 const updatedTodo = await Todo.findByIdAndUpdate(req.params.id, { completed: req.body.completed }, { new: true }); 4 res.json(updatedTodo); 5 } catch (err) { 6 res.status(500).send('Server Error'); 7 } 8});
In the above example, we find the Todo by its ID and update the completed
field specified in the request body using findByIdAndUpdate
. If the update is successful, we return the updated Todo in the response. If an error occurs, we catch it and return a 500 status code with an error message.
The option { new: true }
in findByIdAndUpdate
means that the method should return the updated document rather than the original document. By default, Mongoose returns the original document before the update was applied, but with { new: true }
, it returns the document after the update.
In this lesson, we created and configured an Express server with CORS middlewares. We then connected the server to a MongoDB database using Mongoose. Additionally, we defined a Mongoose schema and model for Todo items and introduced async and await for handling asynchronous code. We implemented CRUD operations (POST, GET, PUT, DELETE) with route handlers and examined the PATCH operation for updating partial details of a resource.
Practice these steps by extending your backend to handle additional fields or constraints. This hands-on approach will strengthen your grasp on building backend servers with Node.js and Express, eventually preparing you for handling more complex backend services. Keep experimenting and happy coding!