Build an Online Class Calendar with Acuity Scheduling’s API

Building an online class calendar with the Acuity Scheduling API, Express.js and Webpack.

Related Course

Get Started with JavaScript for Web Development

JavaScript is the language on fire. Build an app for any platform you want including website, server, mobile, and desktop.

I moonlight (a lot of us do). I moonlight as a Lego Engineer. It's always been a dream of mine, and in the evenings, I've been making it a reality.

At first I would help throw a party now and then — a Lego Party, where I bring the Legos, supply decades of Lego construction expertise, and clean up the mess. When business picked up, I hired an assistant. We started to fill some other niches: one-on-one construction instruction for small, medium and large Lego sets, Lego therapy for stress and IBS relief (Irritable-Builder-Syndrome), adult Lego parties (things can get weird).

Recently, I started offering a regular schedule of evening and weekend classes aimed at helping people become better builders. Managing a few appointments a week was fine, but the phone calls from the class attendees were starting to seriously jeopardize my day job. Tapping into that daytime experience as a software engineer, I decided it was the right time to move that regular schedule into the modern era with an online calendar for booking classes:

Now, I don't want this class calendar to be my life's work, which means using a couple open-source libraries and an existing online booking platform. And I do want to do it right, which means a little up-front design. We'll build this in two pieces:

  1. A Server Component: an API facade, allowing control over which API calls are made to the booking platform. After all, we don't want our API credentials floating around in a client-side application.
    • Acuity Scheduling is the online platform I selected (full disclosure: this is my day job). Acuity has an appointment scheduling API, a backend for managing my availability and appointment reminders, and a front-end allowing clients to cancel and reschedule online.
    • Express.js is an HTTP server suitable for the task of serving the API facade.
  2. A Client Component: a UI for mapping client intention to API calls, e.g., a calendar and some forms ;-)
    • Webpack is a module bundler at heart, and we'll use it to pack up our client-side assets. It's also a helpful development server!
    • FullCalendar is a full-featured calendar plugin for jQuery with good docs and a long history of development.

Let's get started!

#The Part About Acuity Scheduling

For many businesses, online scheduling means more time to do what they love and less time managing their calendar. For clients, it means scheduling an appointment on their terms, say, late in the evening, with a glass of red wine, from a bathtub.

For me, it means I can stop taking calls for my side job at work.

Acuity Scheduling has a developer friendly scheduling platform for customizing my booking process, and all the bells and whistles one expects from a modern online appointment scheduler.

To follow along with this HowTo, a free trial will work just fine. You'll need to create a couple Group Classes to display on the calendar, and you'll need your User ID and API Key from Business Settings - Integrations.

#The Server Component

The server component talks to the Acuity Scheduling API on our behalf, and serves up the page containing the class calendar. To get started, we'll need to install a couple Node modules:

  • acuityscheduling Acuity's JS SDK
  • express A JavaScript HTTP Server

First create a new project, then install the these modules using npm:

$ npm init
$ npm install --save acuityscheduling express

Now that we have the modules installed, we can start building the server. Create a new file called app.js and include the modules:

'use strict';

// Modules:
var AcuityScheduling = require('acuityscheduling');
var express = require('express');

Then create a new instance of the Acuity SDK for accessing the API. We'll use environmental variables to set the Acuity API credentials and other configuration values because it's a quick and easy alternative to config files:

// Create an instance of Acuity for accessing the API:
var acuity = AcuityScheduling.basic({
  "userId": process.env.ACUITY_USER_ID,
  "apiKey": process.env.ACUITY_API_KEY
});

Next, we need to create an instance of the Express server and choose a port to listen on. We'll give the port a default value of 3000, but also let it be configured from the environmental variables:

// Create a new express server:
var app = express();
var port = process.env.ACUITY_PORT || 3000;

Now that we have an app server, we'll add a couple routes to do our real work for us. We'll need one route to serve our client component (for now, let's just say "Hi") and another to get the list of classes from the Acuity API. To display a monthly calendar of classes, we'll use a query string parameter month to tell Acuity which month we want:

// Add some routes:
app.get('/', function (req, res) {
  res.send('Hello World');
});
app.get('/api/classes', function (req, res) {
  // Request classes from the Acuity API:
  acuity.request('/availability/classes?month=' + req.query.month, function (err, response) {
    if (err) {
      return console.error(err);
    }
    res.send(response.body);
  });
});

Now we can start the server:

// Listen on the selected port:
app.listen(port, function (err) {
  if (err) {
    return console.error(err);
  }
  console.log('Server started! Say hey at http://localhost:' + port + '/')
});

Finally, let's take it all for a spin. Start the server, passing your Acuity user ID and API key from Business Settings - Integrations in through the environmental variables:

$ ACUITY_USER_ID = 123 ACUITY_API_KEY=abc node app.js

If all goes well, you'll see the "Server started!" message. Head to http://localhost:3000/to say Hi to your server. Pull up a class list by visiting http://localhost:3000/api/classes?month=2017-02.

#The Part About the Client Component

The client component is our web-based user interface, allowing folks to browse the class calendar and book an appointment. It talks to the server component, which makes calls to the Acuity API and returns a list of classes. We're gonna need a couple more modules:

  • webpack, a bundler for client-side assets
  • FullCalendar, a calendar widget
  • style-loader and css-loader, two webpack plugins we'll be using.

These can also be installed using npm:

$ npm install --save-dev webpack style-loader css-loader fullcalendar

WEBPACK CONFIGURATION

Webpack is an awesomely powerful JavaScript module bundler and CSS bundler, image bundler, extensible bundler, development server — everything but the kitchen sink (and you can probably make a plugin to bundle that too).

Along with this power comes a right mess of configuration options, but we'll keep it simple. Create a new file webpack.config.js and fill it with this:

module.exports = {
  // Configure main client bundle:
  entry: './src/index',
  output: {
    path: './public',
    filename: 'bundle.js'
  },
  // Configure CSS module loader:
  module: {
    loaders: [
      { test: /\.css$/, loader: "style-loader!css-loader" }
    ]
  }
};

Create an empty file src/index.js, you can now run webpack:

$ ./node_modules/.bin/webpack

At this point, we're just creating a big bundle of nothing, but if you've done everything correctly you'll see some output about chunks. More importantly, you should now have a file public/bundle.js. If everything isn't quite right, you'll see a (hopefully) much more helpful error message.

Run ./node_modules/.bin/webpack to rebuild the client component's bundle as we go along, or start webpack in watch mode to continually rebuild the bundle as files change:./node_modules/.bin/webpack --watch

SERVING UP THE CLIENT COMPONENT

Now that we have our asset bundle, create an HTML file to serve it in public/index.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Class Calendar</title>
  </head>
  <body>
    <h1>Class Calendar</h1>
    <div id="class-calendar"></div>
  </body>
  <script type="text/javascript" src="bundle.js"></script>
</html>

We'll wire this up in our existing app.js using the built-in Express middleware for serving static assets. Simply replace the app.get('/', ... route with app.use('/', express.static('public'));. The full list of routes should now be:

// Add some routes:
app.use('/', express.static('public'));
app.get('/api/classes', function (req, res) {
  acuity.request('/availability/classes?month=' + req.query.month, function (err, response) {
    if (err) {
      return console.error(err);
    }
    res.send(response.body);
  });
});

Restart your server and blam: http://localhost:3000/ You've got a skeleton for the client-side UI. You can find the full source code for this step on GitHub.

FULLCALENDAR

Now that we have the skeleton for our UI, we can start putting together the calendar. First, open the empty file src/index.js and include the dependencies:

'use strict';
// Modules:
var $ = require('jquery');
var FullCalendar = require('fullcalendar');

FullCalendar is a jQuery module, and we'll need jQuery to kick things off. Next, we'll add our styles. Remember style-loader and css-loader and the modules config inwebpack.config.js? Using those loaders, we can require CSS stylesheets, and webpack will automatically bundle them into bundle.js and load them at runtime:

// Load styles:
require('fullcalendar/dist/fullcalendar.css');

With the styles in place, we're ready to render the calendar. We'll pass in some initial configuration defining the calendar's header, and some empty callbacks to fetch events and handle clicks.

// Render the calendar on document load.
$(function () {
  $('#class-calendar').fullCalendar({
    // Display the month name and previous/next month buttons:
    header: {
      left: 'prev',
      center: 'title',
      right: 'next'
    },
    // Fetch events from the API and format them for FullCalendar
    events: function (start, end, timezone, callback) { },
    // Open the client scheduler on click:
    eventClick: function (event) { }
  });
});

Re-bundle your app using ./node_modules/.bin/webpack and refresh the page http://localhost:3000/. You'll now see a very empty calendar — let's fill it up!

DISPLAY THE CLASS CALENDAR

Now, we're ready to display our class calendar and make it interactive. FullCalendar's handy events option allows you to asynchronously fetch events to display on the calendar. We'll make a request to our API facade /api/classes, format the response for FullCalendar, and supply the data to the FullCalendar callback:

events: function (start, end, timezone, callback) {
  // Get the month currently in view:
  var month = start.endOf('week').format('YYYY-MM-DD');
  // Formats class data from the API for a FullCalendar event:
  function createCalendarEventFromClassData (classData) {
    return {
      title: classData.name+' ('+(classData.slots - classData.slotsAvailable)+'/'+classData.slots+')',
      start: classData.time,
      color: classData.color,
      data: classData // for the "click" handler
    };
  }
  // And request the classes for that month:
  $.get('/api/classes?month='+month)
    .then(function (classes) {
      // Map the classes data to events for calendars:
      var eventsData = [];
      for (var i = 0; i < classes.length; i++) {
        eventsData.push(
          createCalendarEventFromClassData(classes[i])
        );
      };
      // Send the events data to the calendar:
      callback(eventsData);
    }, function (error) {
      console.error(error);
    });
},

Finally, we can wire up the click handler. We'll use your Acuity user ID and the "data" attribute we supplied to the FullCalendar event to build a direct scheduling link. To get the Acuity user ID on the client side, we'll use a special webpack plugin to define a constant ACUITY_USER_ID. From now on, run webpack with the option --define ACUITY_USER_ID=123.

Lego enthusiasts will use that link to book their class:

// Open the client scheduler on click:
eventClick: function (event) {
  var data = event.data;
  if (typeof ACUITY_USER_ID === 'undefined') {
    console.error('Acuity user ID missing.  Please start webpack with `--define ACUITY_USER_ID=YOUR_ID`');
  }
  window.open('https://app.acuityscheduling.com/schedule.php?' +
    'owner=' + ACUITY_USER_ID +
    '&datetime='+data.time +
    '&calendarID='+data.calendarID +
    '&appointmentType='+data.appointmentTypeID
  );
}

With that final change, it's time to re-bundle with ./node_modules/.bin/webpack --define ACUITY_USER_ID=123. You can find the full changes on GitHub. After rebundling, head back to localhost:3000 and schedule some classes:

#Appendix - Where to Go From Here

The bones of our class calendar are there, but there's plenty more to do! Here are some ideas —

  • Schedule in style using Bootstrap or a custom stylesheet. Just install bootstrap with npm install --save-dev bootstrap and addrequire('bootstrap/dist/css/bootstrap.css) to your src/index.js.
  • Enable CORS for the API facade using the Express CORS middleware. This will enable you to deploy the static assets in /public to your existing website, while hosting the API facade somewhere else.
  • Build a custom appointment booking UI using Acuity's Appointment APIs.

This content is sponsored via Syndicate Ads.