Skip to content

Instantly share code, notes, and snippets.

@cklanac
Last active October 12, 2019 01:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cklanac/c721c8121f6880fff3aa72e96ebc047d to your computer and use it in GitHub Desktop.
Save cklanac/c721c8121f6880fff3aa72e96ebc047d to your computer and use it in GitHub Desktop.
Challenge 02: Express Middleware

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.

Requirements

  • Create a config.js module which exports PORT
  • 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.

Create a config.js module

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 following module.exports statement to the file.
module.exports.PORT = 8080;
  • In server.js, require the config.js module and create a variable for PORT using object destructuring. Here's an example:
const { PORT } = require('./config');
  • Update the app.listen statement. Replace the hard-coded 8080 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!

Create Logger Middleware

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 or PUT
    • 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!

Express Error-Handling Middleware

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!

Add Simple In Memory Database

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.

  1. Find a list of notes that contains the word 'cats'

    notes.filter('cats', (err, list) => {...})
  2. Find a specific note by ID

    notes.find(1005, (err, item) => {...})
  3. 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!

Add Database and Update Endpoints

The next challenge is to add the simDB to your app.

  • In server.js, right after the const 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!

GET Notes with search

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!

GET Notes by ID

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.

Testing your changes using Postman.

  • Create a request which GETs /api/notes/1005.
  • Does it return the correct note?

Commit your changes!

PUT (Update) Notes by ID

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()); to server.js after the app.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();
    }
  });
});

Testing the endpoint using Postman.

  • Create a request which PUTs a JSON body containing a title and content (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 or content property names or PUT'ing extra data. Add a console.log(req.body) and console.log(updateObj) to the code to compare the values.

Commit your changes!

Update Client

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!

Merge and Push

Once you are happy with your solution. Checkout master and merge your feature branch and push your changes to Github.

Solutions

You can view an example solution and compare the differences between branches

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment