Many web developers are likely familiar with the general concept of CRUD. Yet, many of us haven't worked with these concepts in great depth. I thought I'd share a recent experience about a feature I created for a client. In this multi-part tutorial, I cover sessionStorage, REST and some practical database examples. By the end, you'll feel more comfortable creating full-stack features of your own.

What is the feature?

It's a promotional popup containing some information and a link to the full promotion page. And no, it's not the traditionally annoying "in your face" popup (which Google now considers an anti-pattern), but rather, one that hugs the bottom of the screen as you scroll. Once dismissed, it won't return for the duration of the session. If you open a new tab and revisit the site, only then does the popup return.

I thought you said, "full-stack"

When I first built it, it was only HTML/CSS/ client-side JavaScript. At the time, it seemed like the easiest way (sound familiar?). Later, the client asked if he could create and publish his own promotions. My first instinct was to tell him that I could do the updates myself. But there are significant drawbacks to this approach.

  • Updates, no matter how benign, take time. Anyone who has returned to a site they haven't looked at in 3-4 months knows how disorienting atmospheric re-entry can be.
  • Hard-coded changes sometimes need browser testing (i.e. more time).
  • Hard-coded changes increase the likelihood of human error.
  • There is no easy way to save old promotions which the client may wish to feature again later.
  • There is no easy way to turn off promotions.

The path became clear: I would build a feature allowing the client to manage his own promotions. Now that the database entered the mix, the scope of the feature expanded to warrant the full-stack moniker. More importantly, tying the feature to the database resolves all the above issues.

Which languages/frameworks are we using?

As mentioned earlier, the front-end is built with Express, a bare-bones, Node framework. The markup is generated with Pug and the CSS is done with stylus. For the database, we'll use MongoDB. It has a large, active community, and a friendly learning curve for rapid development. I'm using Heroku for hosting, but there are many other Node-friendly hosts from which to choose.

The markup

I prefer seeing what the actual feature will look like first, before the database is even considered. During the markup phase, you'll soon realize what will eventually need to be stored in the DB and what can be hard-coded into the view.

  .promo-popup
  h2 Featured Promotion
  p Promo title goes here
  a.call-to-action-button(href="/featured-promotion") Find out more
  button.close Dismiss

From top to bottom

I decided to leave the h2 value as is. The promotion title, however, as the p value, will be set by the administrator. That means we've uncovered our first database candidate! Since we can only have one promotion showing at a time, it's safe to assume the full page URL and the button text will be constants.

Add it to the layout

    include promo-popup

After a few styles I won't bother including here, here's what we've got:

Session Storage

Users will be annoyed with a popup that shows up every visit and can't be dismissed. Let's change that with a little vanilla JavaScript. This code sets a click event listener on the 'Dismiss' button we've just created. When clicked, it does two things:

  1. Add a close-popup class to the body, so our popup will transition to a closed state

  2. Set our storage key, promo-popup-close, to true. This ensures the popup won't reappear after it's dismissed.

  .promo-popup
  transform: translate(-50%, 0);

  // hides the popup offscreen
  .close-popup &
    transform: translate(-50%, 30em);

  const close_button = document.querySelector('.promo-popup .close');

try {
  if (typeof utilities.getSessionStorage('promo-popup-close') !== 'undefined') {

    if (utilities.getSessionStorage('promo-popup-close') === 'true') {
      document.body.classList.add('close-popup');
    }
  }
} catch(e) {}

close_button.addEventListener('click', () => {
  try {
    utilities.setSessionStorage('promo-popup-close', true);
  } catch(e) {}

  document.body.classList.add('close-popup');
});

try/catch/what?

From experience, I've learned a few gotchas related to Web Storage. One is that, until recently, Safari reports a false positive while in private browsing mode. That is, Safari will say, "Storage? Yes, I support that. Go right ahead." The problem is that Storage is actually not available during private browsing, and when you try to use it, an error will fire, completely breaking your JavaScript. The try/catch block protects us from this case and silently handles the error.

Where is the utilities object?

In a different file, I create a few helper functions and store them in a utilities object. Here are the helper functions for getting and setting a storage key.

  const utilities = {
  setSessionStorage(key, val) {
    sessionStorage.setItem(key, val);
  },

  getSessionStorage(key) {
    return sessionStorage.getItem(key);
  }
};

End of Part 1

We didn't do a whole lot just yet, but things are off to a good start. The markup looks right and closed popups stay closed. In part 2, we'll set up a local database and connect it to the view. Suggestions? Please let me know. Thanks for reading!


1,360 3 14