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.js
module which exportsPORT
- Create a
logger
middleware 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.js
file in the root of the application. And add the followingmodule.exports
statement to the file.
module.exports.PORT = 8080;
- In
server.js
, require theconfig.js
module and create a variable forPORT
using object destructuring. Here's an example:
const { PORT } = require('./config');
- Update the
app.listen
statement. Replace the hard-coded8080
value 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.js
file - In
logger.js
create an Express Middleware function which logs the following for all the incoming requests:- Current date
- Request
method
like:GET
,POST
orPUT
- Request
url
like:/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=ways
Commit 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
simDB
are 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.js
The 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 this
Next, 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/notes
endpoint 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.js
after 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
title
andcontent
(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
title
orcontent
property 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-middleware
branch in the upstream repo. - Compare this solution branch to the previous