For this challenge, you'll create a config module and add 3 pieces of middleware: a logger, a custom 404 handler and a custom 500 error handler.
Then you'll update the client to take advantage of the PUT endpoint by completling the event listener and adding new AJAX call to allow users to update notes.
- Create a
config.jsmodule which exportsPORT - Create a
loggermiddleware which logs all requests to the server - Add a custom 404 Handler middleware after the normal routes
- Add a custom Error Handler middleware as the last piece of middleware (just before
app.listen) - Wire up the
simDB - Update the existing GET endpoints with database queries
- Create a PUT endpoint which accepts an ID and updates a note
- Update the sample client app to take use the new features
Before getting started, remember to create a development branch git checkout -b feature/middleware-modules to hold your work.
Your first challenge is to create a config module which exports the PORT for the server. The module will start off simple, but you'll add more in later challenges.
- Create a
config.jsfile in the root of the application. And add the followingmodule.exportsstatement to the file.
module.exports.PORT = 8080;- In
server.js, require theconfig.jsmodule and create a variable forPORTusing object destructuring. Here's an example:
const { PORT } = require('./config');- Update the
app.listenstatement. Replace the hard-coded8080value with the variable.
It should look something like this:
app.listen(PORT, function () {...Verify your changes in Postman. It should work as before. Test is by changing the PORT number to something like 8081, rerun and test your app. Remember to change the port number back to 8080.
Commit your changes!
The next challenge is to create a logger similar to the one presented in the curriculum. Since you have already seen the material we'll simply provide the bullet points as guidelines.
- Create
/middleware/logger.jsfile - In
logger.jscreate an Express Middleware function which logs the following for all the incoming requests:- Current date
- Request
methodlike:GET,POSTorPUT - Request
urllike:/api/notes
As you build your logger, test it using Postman. Each request should be logged to the console, for example:
2/20/2020, 12:34:56 AM GET /api/notes
2/20/2020, 12:34:56 AM GET /api/notes?searchTerm=waysCommit your changes!
The next challenge is to create Express Middleware to handle 404's and Error-Handling Middleware to deal with runtime errors. We'll provide the details.
- Open your
server.js - Add the following 404 handler middleware after the normal routes but before the
app.listen
app.use(function (req, res, next) {
var err = new Error('Not Found');
err.status = 404;
res.status(404).json({ message: 'Not Found' });
});Test the 404 error handler by entering an invalid URL in Postman such as GET /api/does/not/exist. The API should respond with a HTTP 404 status and a JSON object { message: 'Not Found' }, instead of the default HTML.
Next, you'll add a custom error handler middleware. Again, we'll provide the details.
- Add the following directly after the 404 handler you created above.
app.use(function (err, req, res, next) {
res.status(err.status || 500);
res.json({
message: err.message,
error: err
});
});If a runtime error occurs in an Express, it will immediately propagate to the next error handler with the method signature: app.use(function (err, req, res, next) {...}).
Testing runtime errors is tricky since we usually don't like to generate errors in our code. So, temporarily add the following endpoint to your server.js.
app.get('/boom', (req, res, next) => {
throw new Error('Boom!!');
});Verify the error handler using Postman. Call the GET /boom endpoint. Do you see the correct result?
Commit your changes!
Included in the project starter is a simple in-memory database named simDB in /db/simDB.js. The simDB is just a set of Array methods, not a real database. Each time you restart your app the data will be reset to the data in notes.json.
The details of the
simDBare interesting but not important right now. If you have time, then come back and review the code.
Let's experiment with the simDB to get a feel for how it works. A good way to do this is using a "scratch file".
- Create a directory named
scratch - Create a file named
queries.js - Add the following block of code
const data = require('../db/notes');
const simDB = require('../db/simDB');
const notes = simDB.initialize(data);
// GET Notes with search
notes.filter('cats', (err, list) => {
if (err) {
console.error(err);
}
console.log(list);
});
// GET Notes by ID
notes.find(1005, (err, item) => {
if (err) {
console.error(err);
}
if (item) {
console.log(item);
} else {
console.log('not found');
}
});
// PUT (Update) Notes by ID
const updateObj = {
title: 'New Title',
content: 'Blah blah blah'
};
notes.update(1005, updateObj, (err, item) => {
if (err) {
console.error(err);
}
if (item) {
console.log(item);
} else {
console.log('not found');
}
});- Run the scratch file in the terminal:
nodemon scratch/queries.jsThe file loads and initializes the simdb, then makes 3 queries.
-
Find a list of notes that contains the word 'cats'
notes.filter('cats', (err, list) => {...})
-
Find a specific note by ID
notes.find(1005, (err, item) => {...})
-
Update a specific note
notes.update(1005, updateObj, (err, item) => {...})
Experiment with the commands, change the filter and search values and log the results. Try the following:
- Create a new note using the
.create()method - Delete a new note using the
.delete()method
Commit your changes!
The next challenge is to add the simDB to your app.
- In
server.js, right after theconst data = require('./db/notes');add the following statements to load and initialize the sim database.
// Simple In-Memory Database
const data = require('./db/notes');
const simDB = require('./db/simDB'); // <<== add this
const notes = simDB.initialize(data); // <<== and thisNext, you'll update the endpoints to use the simDB. We'll help you with the first endpoint so you can do the second endpoint on your own.
Commit your changes!
Next you'll update the GET /api/notes endpoint with the notes.filter query from above. Below is sample code. Notice how the notes.filter query used console.log() to output data but the API needs return JSON so we've updated the output. And if there is an error, we call next(err) instead of console.error(err).
- Update the GET
/api/notesendpoint in your server.js with the following:
app.get('/api/notes', (req, res, next) => {
const { searchTerm } = req.query;
notes.filter(searchTerm, (err, list) => {
if (err) {
return next(err); // goes to error handler
}
res.json(list); // responds with filtered array
});
});Test the endpoints using Postman.
Commit your changes!
Your turn!
The next challenge is to update the GET /api/notes/:id with the notes.find() query from above. We provided the previous example, this one you'll do on your own.
- Create a request which GETs
/api/notes/1005. - Does it return the correct note?
Commit your changes!
Great! Now let's add a new endpoint to update a note. We'll do this together.
Updating a note requires access to the request body which means we need to tell the Express app to utilize the built-in express.json() middleware. The express.json() middleware parses incoming requests that contain JSON and makes them available on req.body
- Add
app.use(express.json());toserver.jsafter theapp.use(express.static('public'));middleware.
This portion of your server.js should look something like this:
...
// Create an Express application
const app = express();
// Log all requests
app.use(logger);
// Create a static webserver
app.use(express.static('public'));
// Parse request body
app.use(express.json());
...Now, we're ready to add the PUT endpoint.
- Add the following in your
server.js.
app.put('/api/notes/:id', (req, res, next) => {
const id = req.params.id;
/***** Never trust users - validate input *****/
const updateObj = {};
const updateFields = ['title', 'content'];
updateFields.forEach(field => {
if (field in req.body) {
updateObj[field] = req.body[field];
}
});
notes.update(id, updateObj, (err, item) => {
if (err) {
return next(err);
}
if (item) {
res.json(item);
} else {
next();
}
});
});- Create a request which PUTs a JSON body containing a
titleandcontent(see below) to/api/notes/1005.
{
"title": "12 things lady gaga has in common with cats",
"content": "Posuere sollicitudin aliquam..."
}
- Then retrieve the same note by creating a request which GETs
/api/notes/1005. Did the note update properly? - Spend a few minutes exploring and experimenting with the code. Try changing the
titleorcontentproperty names or PUT'ing extra data. Add aconsole.log(req.body)andconsole.log(updateObj)to the code to compare the values.
Commit your changes!
You're in the homestretch!
The server now has 3 endpoints:
- GET
/api/notes- returns list of notes - GET
/api/notes/:id- returns a specific note - PUT
/api/notes/:id- updates a note
Now, let's update the client to take advantage of the new features.
In the /public/scripts/noteful.js file, find the handleNoteFormSubmit placeholder function
function handleNoteFormSubmit() {
$('.js-note-edit-form').on('submit', function (event) {
event.preventDefault();
console.log('Submit Note, coming soon...');
});
}And update it with the following.
function handleNoteFormSubmit() {
$('.js-note-edit-form').on('submit', function (event) {
event.preventDefault();
const editForm = $(event.currentTarget);
const noteObj = {
title: editForm.find('.js-note-title-entry').val(),
content: editForm.find('.js-note-content-entry').val()
};
noteObj.id = store.currentNote.id;
api.update(noteObj.id, noteObj, updateResponse => {
store.currentNote = updateResponse;
render();
});
});
}Remember to add the method to bindEventListeners.
function bindEventListeners() {
handleNoteItemClick();
handleNoteSearchSubmit();
handleNoteFormSubmit(); // <<== Add this line
}Last but not least, let's wire up a new AJAX request to the PUT endpoint. Add the following method to the /public/scripts/api.js file.
...
update: function(id, obj, callback) {
$.ajax({
type: 'PUT',
url: `/api/notes/${id}`,
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(obj),
success: callback
});
}
...Once again, take the time to review the code and make sure you understand it and how it interacts with the rest of the app.
Test it out. Start your server and test the API with the client in the browser.
- Does the list of notes appear when you load the page?
- Does a specific note populate the form when you click on the list?
- Can you search the list of notes by title?
- Can you edit a note and save the changes?
Commit your changes!
Once you are happy with your solution. Checkout master and merge your feature branch and push your changes to Github.
You can view an example solution and compare the differences between branches
- Solution: You can find the example solution in the
solution/02-middlewarebranch in the upstream repo. - Compare this solution branch to the previous