Online Booking Tutorial

Cronofy’s calendar synchronization and scheduling API enables a rich suite of functionality in any app. Combined with the companion UI Elements, getting started is a quick and straightforward process. This tutorial will guide you through the process of integrating Cronofy into your own application. You’ll see for yourself just how easy it is to check availability and schedule an event.

What we’ll be building #

We’ll create a three-page application that will allow you to connect a calendar account, view your availability, and create a new event.

For page one, we’ll add the Calendar Sync UI Element and cover the steps necessary to authenticate a new calendar profile.

For page two, we’ll use the Availability Viewer Element to view your availability and select a time-slot to book your event.

And in page three, we’ll add the new event to your calendar and display a confirmation.

Before you start #

Creating a new app in the Cronofy dashboard #

Before we can create our own application, we need a Cronofy account to connect to. Log into your account, and navigate to the “Applications” section of the dashboard. There you’ll see the “Create new app” button; clicking this will generate a new application with a new client ID and client secret. You’ll need these to authenticate your own app with the Cronofy API, and they can be found in the “settings” tab.

Setup your own application #

You’ll also need to setup the scaffolding for your application in whichever language or framework you’re most comfortable with. For this tutorial we’ll be using Node.js, and the app structure and routing will be handled by Express.js, with EJS as our templating language.

If you want to follow along with this tutorial, you can download the starter-project here: Developer Demo Starter repo.

This will provide you with a working Express app; it comes with all the routing already set up for you. Follow the instructions in that repo’s README.md to download the dependencies and start the app server.

For our app, the folder structure will look like this:

app/
    app.css
    server.js
    templates/
        availability.ejs
        home.ejs
        submit.ejs
.env
.gitignore
package.json
README.md
  • .env is where we’ll store our client key and client secret (for our project, this file is ignored by GIT).
  • app.css is a stylesheet that contains the basic styles for this project.
  • package.json stores the information about our Node dependencies. Running npm install after the repo has been downloaded will install all the dependencies into a node_modules/ directory in the root of the project.
  • server.js is where we configure our Express settings and add the server-side portions of our code.
  • The templates/ directory contains .ejs templates to provide the markup for each page, and the frontend portions of our code will be placed in these files (using inline <script> tags).

Step 1. Setup the SDK #

We’ll be making use of the cronofy-node SDK. There are Cronofy SDKs for most major languages, and the concepts covered in this tutorial will apply to whichever language you choose to use.

cronofy-node can be installed in your project using npm. To install, navigate to the root directory of your project and run this command:

npm install --save cronofy

This will install the cronofy package into your project’s node_modules directory, and add the details to the dependencies object in your package.json file. Once Cronofy Node has been installed, you can import it into your JavaScript files with a require() statement:

const Cronofy = require("cronofy");

Once you’ve included the SDK, the next step is to instantiate a new instance with your own credentials (the client ID and client secret we created in the Before you start section). It’s important to keep these credentials secure, and part of that security includes keeping your credentials out of version control. For that reason, we’ve included the dotenv package in our starter-project, which allows us to set our credentials as environment variables. We’ll store these variables in our .env file like this:

// .env
CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_SECRET="YOUR_CLIENT_SECRET"
ACCESS_TOKEN="YOUR_ACCESS_TOKEN_GOES_HERE"
SUB="YOUR_SUB_GOES_HERE"

This allows us to use our environment variables with the process.env.VARIABLE_NAME format. We’ve already shown how to find your CLIENT_ID and CLIENT_SECRET. A SUB will be required when we generate our element tokens in Step 2. In a real application, you can retrieve the subs of connected profiles using the userinfo endpoint of the API. The ACCESS_TOKEN will be required when we add events to a calendar in Step 4.

Add const Cronofy = require("cronofy"); to the top of the server.js file, alongside the other dependencies’ require() statements. Then add the following code to initialize a cronofyClient instance:

const cronofyClient = new Cronofy({
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET
});

Step 2. The “Home” page #

Now we’ve setup our cronofyClient instance, it’s time to put it use. The first page in our application will be our “home” page where we can add and remove connected calendar profiles, and we can achieve this functionality by making use of the Calendar Sync UI Element.

Introduction to UI Elements #

Cronofy’s UI Elements are a suite of embeddable JavaScript components that provide user interface overlays to various Cronofy API endpoints. To be able to use the UI Elements within your own application, you need to include the Element’s source .js file into your page:

https://elements.cronofy.com/js/CronofyElements.v1.8.1.js

Once you’ve included the .js file in your project, you can initialize the desired Element using the corresponding method and passing in an options as an object.

Element tokens #

UI Elements require an Element-specific token to handle authentication. To generate a token using the Cronofy Node SDK, you can use the requestElementToken() method. To generate a valid Element token, you will need to know the sub for each profile you want to be able to access. For simplicity, we’ve alreadt added our own personal sub as an environment variable (SUB) in our .env file. In a real application, you can retrieve the subs of connected profiles using the userinfo endpoint of the API.

In the server.js file, find the "/" route for the app:

app.get("/", async (req, res) => {
    return res.render("settings", {});
});

And replace it with this:

app.get("/", async (req, res) => {
    const token = await cronofyClient.requestElementToken({
        version: "1",
        permissions: ["account_management"],
        subs: [process.env.SUB],
        origin: "http://localhost:7070/"
    });

    return res.render("home", {
        element_token: token.element_token.token,
        client_id: process.env.CLIENT_ID
    });
});

That code generates an Element token (asynchronously) and passes both the element token and the client ID to the page template “home.ejs”.

You can view the full set of authentication options in the UI Elements’ Authentication documentation.

Adding your first UI Element #

Armed with our new token, we can now add an Element to our page using the home.ejs template.

Every UI Element needs a mounting point on the page. This can be any DOM node you like, provided it has a unique html ID. To add the Calendar Sync Element to our page, we’ll create a <div> with an ID of cronofy-calendar-sync. We’ll use this ID to tell the UI Element script where to put the Element.

Along with the token and mounting point, the Calendar Sync Element requires some authorization options. We’ll need to include a redirect_uri, your client id, and a scope (for this Element the scope is always “read_write”). These are used when a new calendar profile is added, as they are required by the oAuth process.

Look for the empty <script> tag toward the bottom of the home.ejs template, and replace it with the following code:

<div id="cronofy-calendar-sync"></div>

<script src="https://elements.cronofy.com/js/CronofyElements.v1.8.1.js"></script>
<script>
    CronofyElements.CalendarSync({
        element_token: "<%= element_token %>",
        target_id: "cronofy-calendar-sync",
        authorization: {
            redirect_uri: "http://localhost:7070",
            client_id: "<%= client_id %>",
            scope: "read_write"
        }
    });
<script/>

With that script included in your page, the Calendar Sync Element will be rendered inside the “cronofy-calendar-sync” <div>.

Code redemption #

There’s one more step before the Calendar Sync Element setup is complete. If we load the page as-is, we’ll be presented with a choice of calendar providers and a message saying “Select a provider to add your first account”.

This means we need to connect a calendar profile to our application, and to do that we need to be able to complete the authorization process. Users start the authorization flow by clicking one of the provider links, and when the process is complete the user is returned to the redirect_uri with a code included as a URL parameter.

To redeem the code and complete the addition of the new profile, we use the code to request an access token. The new access token is not required within the Element, but needs to be requested to complete the authorization process. This request requires your Client ID and Client Secret, so must take place server-side.

Add the following to the start of the "/" route in server.js:

app.get("/", async (req, res) => {
    // Extract the "code" from the page's query string:
    const codeFromQuery = req.query.code;

    if (codeFromQuery) {
        const codeResponse = await cronofyClient.requestAccessToken({
            client_id: process.env.CLIENT_ID,
            client_secret: process.env.CLIENT_SECRET,
            grant_type: "authorization_code",
            code: codeFromQuery,
            redirect_uri: "http://localhost:7070"
        });
    }
    
    // ...token generation and template rendering
});

Step 3. View your availability #

For the second page of our application, we want to be able to view our availability for a set period of time, and to be able to select a time slot to create a new event. We can do this with the Availability Viewer UI Element.

Generating a token for the Availability Viewer #

As with the Calendar Sync Element, we will need a new element token to allow the Availability Viewer to access our availability. This process is almost exactly the same as generating a token for the Calendar Sync Element; the only difference is in the “permissions” we need. For the Calendar Sync Element we needed the account_management permission, but for the Availability Viewer we’ll need the availability permission.

In the server.js file, find the "/availability" route for the app:

app.get("/availability", async (req, res) => {
    return res.render("availability", {});
});

And replace it with this:

app.get("/availability", async (req, res) => {
    const token = await cronofyClient.requestElementToken({
        version: "1",
        permissions: ["availability"],
        subs: [process.env.SUB],
        origin: "http://localhost:7070"
    });

    return res.render("availability", {
        element_token: token.element_token.token,
        sub: process.env.SUB
    });
});

Adding the Availability Viewer into the page #

As with our home page, look for the empty <script> tag toward the bottom of the availbility.ejs template, which is where we’ll add our initialization code for the Availability Viewer.

In addition to requiring a different permission-set for the token, for the Availability Viewer to work in this scenario it requires two additional options: an availability_query and a callback. We’ll add these options in stages. For the following code, AVAILABILITY_QUERY and CALLBACK are placeholders that we’ll flesh out in the next steps.

<div id="cronofy-availability-viewer"></div>

<script src="https://elements.cronofy.com/js/CronofyElements.v1.8.1.js"></script>
<script>
    CronofyElements.AvailabilityViewer({
        element_token: "<%= element_token %>",
        target_id: "cronofy-availability-viewer",
        availability_query: AVAILABILITY_QUERY,
        callback: CALLBACK
    });
</script>

Now we need to construct and availability query and our callback.

Constructing an availability query #

In short the availability_query is an object that matches a valid Cronofy Availability request. This query defines three things:

  1. The participants that we want to query.
  2. The required duration of the event we want to book.
  3. An array of periods to check availability in.

For this application, our participants object can be relatively simple. We only want to check our own availability, so we only need to add our own profile (using the sub of our account):

participants: [
    {
        required: "all",
        members: [
            {
                sub: "<%= sub %>"
            }
        ]
    }
]

The required_duration is set in minutes, and can be either 15, 30, or 60.

available_periods should be an array of objects with a start and end time. Both should be a valid Time data-type, and must represent a future point in time. The end of an available period must also be between 1 minute and 35 days after the corresponding start. For example:

available_periods: [
    {
        start: "2019-12-10T09:00:00Z",
        end: "2019-12-10T10:00:00Z"
    },
    {
        start: "2019-12-10T15:00:00Z",
        end: "2019-12-10T17:30:00Z"
    }
]

The Availability Viewer callback #

The Availability Viewer callback is a function to be called when a selected slot is “confirmed” by the user. It receives a notification object with a type of "slot_selected".

For our application, we want to check that the notification represents a “slot_selected” event, and then navigate the browser to the last page in our app. The last page will be where we add the booked event into our calendar, so we’ll need to pass along the selected slot’s details when the last page is loaded. We can do this by adding the slot as a query string. As well as a type, the notification object also contains the slot information that we need.

callback = res => {
    // Check if this is a `slot_selected` notification.
    // If not, we'll return and do nothing.
    if (res.notification.type !== "slot_selected") return;
    
    // Convert the slot info into a URL-safe string.
    const slot = JSON.stringify(res.notification.slot);

    // Load the last page of our app, with the `slot` in the query string.
    window.location.href = `/submit?slot=${slot}`;
};

The full Availability Viewer initialization #

Putting together the availability query and the callback, the Availability Viewer initialization should look something like this:

const availabilityViewerOptions = {
    element_token: "<%= element_token %>",
    target_id: "cronofy-availability-viewer",
    availability_query: {
        participants: [
            {
                required: "all",
                members: [
                    { sub: "<%= sub %>" }
                ]
            }
        ],
        required_duration: { minutes: 60 },
        available_periods: [
            { start: "2019-12-10T09:00:00Z", end: "2019-12-10T10:00:00Z" },
            { start: "2019-12-10T15:00:00Z", end: "2019-12-10T17:30:00Z" }
        ]
    },
    callback: res => {
        if (res.notification.type !== "slot_selected") return;
        
        const slot = JSON.stringify(res.notification.slot);
        window.location.href = `/submit?slot=${slot}`;
    }
};

And the page itself should look like this:

Step 4. add an event to our calendar #

The last page of our app is where we’ll actually add an event to our calendar. We’re being given the event details in the slot query parameter, so we’ll need to parse that into an object we can use. Because we’re going to be writing to a calendar, we’ll need to add an access_token to our cronofyClient.

const cronofyClient = new Cronofy({
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    access_token: process.env.ACCESS_TOKEN
});

Formatting an event #

To add an event to a calendar, we’ll need to turn our slot into an object that will match the format required by the Create or Update Event section of the API.

In addition to the start and end that were given to us by the Availability Viewer callbacl, we’ll need to add a unique event_id, a text summary and description, and the calendar_id of the calendar we want to add the event to.

If you don’t know the calendar_id for your account, you can use the userInfo() method in the SDK, or hit the User Info endpoint directly. This will give you all the necessary information about your account.

Adding the event to the calendar #

With a properly formatted event object, all we need to do to add it to the calendar is submit it using the createEvent() method in the SDK.

For this process to work, the method requires a calendar_id. This is the ID for the specific calendar that the event will be added to. To keep this tutorial simple, we’ll add your calendar’s ID as an environment variable (just like we did with your SUB in the Element tokens section). You can find more information about access tokens in the Request an Access Token section of the API documentation.

In the server.js file, find the "/submit" route for the app:

app.get("/submit", async (req, res) => {
    return res.render("submit", {});
});

And replace it with this:

app.get("/submit", async (req, res) => {
    
    // Get the `slot` data from the query string
    const slot = JSON.parse(req.query.slot);

    cronofyClient.createEvent({
        calendar_id: process.env.CALENDAR_ID,
        event_id: "booking_demo_event",
        summary: "Demo meeting",
        description: "The Cronofy developer demo has created this event",
        start: slot.start,
        end: slot.end
    });

    return res.render("submit", {});
});

Find out more detail about adding an event in the API documentation.

Display a confirmation message to the user #

To complete the application we’ll pass the slot details through to our page template and display a confirmation screen to show the user that the event has been added.

To easily format our date strings, we’ll add Moment.js to our project. To do this we’ll run npm install --save moment (which will install Moment into our node_modules directory) and add const moment = require("moment"); to the top of our server.js file.

With Moment installed, we can format the slot’s date into readable strings that we can then pass into our template.

At the bottom of the "" route in server.js, replace this:

return res.render("submit", {});

With this:

const meetingDate = moment(slot.start).format("DD MMM YYYY");
const start = moment(slot.start).format("LT");
const end = moment(slot.end).format("LT");

return res.render("submit", {
    meetingDate,
    start,
    end
});

Which will allow us to display the date, start-time, and end-time in our submit.ejs template:

<div class="block block--border">
    <p>Event details:</p>
    <p><strong>Date:</strong> <%= meetingDate %></p>
    <p><strong>Time:</strong> <%= start %> to <%= end %></p>
</div>

And the full completed page will look like this:

Step 5. Optional extra: set your rules #

Availability Rules #

If all that wasn’t enough to get you excited about using Cronofy to handle scheduling in your application, there’s more! It’s often useful to define periods of time that you don’t want to be available for, but haven’t explicitly blocked-off in your calendar. A common version of this is setting “work hours”: times in the week that you know you’re always unavailable. Cronofy call these Availability Rules.

Update the Element token #

If we return to our home page (where we currently just have the Calendar Sync Element), we can add in another handy UI Element: the Availability Rules Element.

As with the previous two Elements we’ve used, the Availability Rules Element requires a specific permission when creating its token: this time it’s managed_availability. By adding this permission to the existing requestElementToken options (keeping account_management for the Calendar Sync Element), we can use the same token for both the Elements on the page.

const token = await cronofyClient.requestElementToken({
    version: "1",
    permissions: ["account_management", "managed_availability"],
    subs: [process.env.SUB],
    origin: "http://localhost:7070"
});

Add the Availability Rules Element #

To add the Availability Rules Element into the page, we’ll create another mounting point in the DOM (this time a <div> with an ID of “cronofy-availability-rules”). In addition to the mounting-point ID and the token, we’ll need two extra options: the timezone ID and a unique ID for our rule.

The rule’s ID can be anything you like - it’s what we’ll use to apply the rule to the Availability Viewer’s query (in the next step). The timezone ID must be a known time zone identifier from the IANA Time Zone Database. We can use Intl.DateTimeFormat() to deduce the timezone directly from the browser.

// Sniff the browser's timezone
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;

CronofyElements.AvailabilityRules({
    element_token: "<%= element_token %>",
    target_id: "cronofy-availability-rules",
    availability_rule_id: "work_hours",
    tzid: timezone
});

This will display the Availability Rules Element on the page.

Apply the rule to our Availability Viewer #

Rules are not automatically taken into account by availability queries. If we want the rules to apply, we have to explicitly declare them. This means updating the participants portion of the query that we pass into the Availability Viewer initialization.

Specifically, we need to adjust each member that we want to apply rules to. We do this by declaring managed_availability as true.

participants: [
    {
        required: "all",
        members: [
            {
                sub: "<%= sub %>",
                managed_availability: true
            }
        ]
    }
]

Now that you’ve experienced the power of the Cronofy API first hand, why not explore further? There’s plenty of information in the rest of the documentation site.

Next steps

Search