Tutorial: Building a Museum Booking App with Vue.js and Timerise API

This tutorial will guide you through creating a museum booking app using Vue.js, TypeScript, Timerise API, Apollo Client, and Tailwind CSS. We will focus on integrating the headless Timerise API, demonstrating the benefits of such a solution.

Prerequisites

  • Basic knowledge of TypeScript and Vue.js
  • Node.js installed
  • Access to Timerise API

Step 1: Setting Up the Vue.js Project

The first step in building our museum booking app is to set up a new Vue.js project. Vue CLI provides a flexible and straightforward way to set up new projects. This approach allows us to configure essential features like Tailwind CSS, ESLint, and more, right from the start, simplifying the initial project setup.

Create a New Vue.js App
Run the command to initiate the creation of a new Vue.js app. You’ll be able to integrate Tailwind CSS and other configurations during the setup process.

Bash
npm create vue@latest vue-museum-booking-app
cd vue-museum-booking-app
npm install
npm run format
npm run dev

When prompted, choose your preferred configurations:

  • Add TypeScript? … No / Yes
  • Add JSX Support? … No / Yes
  • Add Vue Router for Single Page Application development? … No / Yes
  • Add Pinia for state management? … No / Yes
  • Add Vitest for Unit Testing? … No / Yes
  • Add an End-to-End Testing Solution? › No
  • Add ESLint for code quality? … No / Yes
  • Add Prettier for code formatting? … No / Yes

Step 2: Installing Dependencies

With our Vue.js project set up, we now need to install additional packages crucial for our application, particularly Apollo Client for handling GraphQL queries and mutations with the Timerise API.

Install Apollo Client
Apollo Client is a comprehensive state management library that enables you to manage both local and remote data with GraphQL. It will be our primary tool for interacting with the Timerise API.

Bash
npm install @apollo/client graphql
npm install @vue/apollo-option @vue/apollo-components --save 

Additional Dependencies
Depending on your project requirements, you may need other dependencies. For example, if you plan to handle dates and times (common in a booking app), consider installing date-fns:

Bash
npm install date-fns

Step 3: Configuring Apollo Client

Now that our project is set up with the necessary dependencies, it’s time to configure Apollo Client. This setup enables our application to efficiently execute queries and mutations, manage loading states, cache data, and handle errors.

Set Up Apollo Client in lib/apolloClient.ts

Create an instance of Apollo Client pointing to the GraphQL API endpoint. This instance will be used throughout the application.

TypeScript
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core'

const httpLink = createHttpLink({
  uri: 'https://sandbox-api.timerise.io/v1',
})

const cache = new InMemoryCache()

export const apolloClient = new ApolloClient({
  link: httpLink,
  cache,
})
  • The uri property is set to the endpoint of the Timerise API.
  • InMemoryCache is used to cache query results after fetching them. This helps in optimizing the performance of our application by reducing the number of API calls needed.

This configuration is a crucial step in enabling our application to communicate effectively with the Timerise API. It sets the stage for the next steps where we will be creating and executing GraphQL queries and mutations to handle bookings.

Wrap Your Application with ApolloProvider
In your main.ts (or main.ts if using TypeScript), import ApolloProvider and the Apollo Client instance. Then, use Vue’s global properties feature to provide the Apollo Client instance globally.

TypeScript
import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

import { apolloClient } from './lib/apolloClient'
import { createApolloProvider } from '@vue/apollo-option'

const apolloProvider = createApolloProvider({
  defaultClient: apolloClient,
})

const app = createApp(App)

app.use(apolloProvider)

app.use(router)

app.mount('#app')

Step 4: Creating GraphQL Queries and Mutations

Define the GraphQL queries and mutations needed to interact with the Timerise API for fetching services and booking slots in src/graphql/queries.ts and src/graphql/mutations.ts.

Define GraphQL Operations in graphql/queries.ts

TypeScript
import { gql } from '@apollo/client/core';

export const SERVICE_QUERY = gql`
  query Service($serviceId: ID!, $slotType: SlotType!) {
    service(serviceId: $serviceId) {
      serviceId
      title
      description
      media {
        title
        url
      }
      slots(slotType: $slotType) {
        slotId
        dateTimeFrom
        dateTimeTo
        duration
        quantity
      }
    }
  }
`;

export const BOOKING_QUERY = gql`
  query Booking($bookingId: ID!) {
    booking(bookingId: $bookingId) {
      bookingId
      shortId
      shortUrl
      qrUrl
      status
    }
  }
`;

Define Mutation in graphql/mutations.ts

TypeScript
import { gql } from '@apollo/client/core';

export const BOOKING_CREATE_MUTATION = gql`
  mutation BookingCreate($serviceId: ID!, $slots: [ID]) {
    bookingCreate(serviceId: $serviceId, slots: $slots) {
      bookingId
      qrUrl
      shortUrl
      status
    }
  }
`;

Step 5: Building the Application Components

With our API interactions defined, we now focus on building the React components. These components will provide the interface for selecting slots and confirming bookings, utilizing the queries and mutations we’ve set up.

Slot Selection Component (SlotSelection.vue)
Code and explanation for creating a component to select available slots.

TypeScript
<template>
  <div>
    <h2>Select a slot to book</h2>
    <ul>
      <li v-for="slot in service.slots" :key="slot.slotId">
        <a
          @click="handleSlotSelect(slot.slotId)"
        >
          {{ new Date(slot.dateTimeFrom).toLocaleString() }}
      </a>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import { provideApolloClient } from '@vue/apollo-composable';
import { apolloClient } from '../lib/apolloClient';
import { defineComponent, ref, watchEffect } from 'vue'
import { useQuery, useMutation } from '@vue/apollo-composable'
import { SERVICE_QUERY } from '@/graphql/queries'
import { BOOKING_CREATE_MUTATION } from '@/graphql/mutations'
import { useRouter } from 'vue-router'

export default defineComponent({
  props: {
    serviceId: String
  },
  setup(props) {
    provideApolloClient(apolloClient);
    const router = useRouter()
    const selectedSlot = ref<string | null>(null)
    const { result, loading, error } = useQuery(SERVICE_QUERY, () => ({
      serviceId: props.serviceId,
      slotType: 'AVAILABLE'
    }))

    const service = ref<any>({});

    watchEffect(() => {
      if (result.value && result.value.service) {
        service.value = result.value.service;
      }
    });

    const {
      mutate: createBooking,
      loading: bookingLoading,
      error: bookingError
    } = useMutation(BOOKING_CREATE_MUTATION)

    const handleSlotSelect = async (slotId: string) => {
      selectedSlot.value = slotId;
      try {
        const result = await createBooking({ serviceId: props.serviceId, slots: [slotId] });
        if (result && 'data' in result) {
          const { data } = result;
          if (data.bookingCreate && data.bookingCreate.bookingId) {
            router.push(`/confirmation/${data.bookingCreate.bookingId}`);
          }
        } else {
          console.error('No data returned from booking creation');
        }
      } catch (error) {
        console.error('Error creating booking:', error);
        // Handle booking error here
      }
    };

    return {
      service,
      loading,
      error,
      bookingLoading,
      bookingError,
      selectedSlot,
      handleSlotSelect
    }
  }
})
</script>

Confirmation Component (BookingConfirmation.vue)
Code and explanation for creating a component that displays booking confirmation details, including the QR code.

TypeScript
<template>
  <div v-if="!loading && !error" style="text-align: center;">
    <h2>Booking status: {{ booking.status }}</h2>
    <p style="margin-bottom: 8px;">Booking ID: {{ booking.shortId }}</p>
    <div>
      <img :src="booking.qrUrl" alt="QR Code" width="200" />
    </div>
    <a :href="booking.shortUrl" target="_blank">View booking page</a>
  </div>
  <p v-if="loading">Loading booking...</p>
  <p v-if="error">Error loading booking.</p>
</template>

<script lang="ts">
import { provideApolloClient } from '@vue/apollo-composable';
import { apolloClient } from '../lib/apolloClient';
import { defineComponent, ref, watchEffect } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import { BOOKING_QUERY } from '@/graphql/queries'

export default defineComponent({
  props: {
    bookingId: {
      type: String,
      required: true,
    },
  },
  setup(props) {
    provideApolloClient(apolloClient);

    const { result, loading, error } = useQuery(BOOKING_QUERY, () => ({
      bookingId: props.bookingId,
    }));

    const booking = ref<any>({});

    watchEffect(() => {
      if (result.value && result.value.booking) {
        booking.value = result.value.booking;
      }
    });

    return { booking, loading, error };
  },
});
</script>

Step 6: Deploying with Netlify or Vercel

After building your Vue.js app, you can choose to deploy it using Netlify or Vercel for Vue.js projects. Ensure your project is pushed to a GitHub repository, then follow the respective platform’s documentation for deploying Vue.js applications.

Push Your Code to GitHub
Before deploying, ensure your project is pushed to a GitHub repository:

Bash
git init
git add .
git commit -m "Init commit"
git branch -M main
git remote add origin https://github.com/your-username/your-repo-name.git
git push -u origin main

Set Up Deployment on Vercel

  1. Sign in to Vercel and connect your GitHub account.
  2. Choose the repository you just pushed.
  3. Vercel automatically detects it’s a Next.js app and suggests build settings. Accept them or modify as needed.
  4. Click ‘Deploy‘ to start the deployment process. Vercel handles the build and deployment, providing you with a live URL.

Vercel provides features like automatic HTTPS, custom domain support, and real-time analytics. You can also set environment variables and manage other settings directly from the Vercel dashboard.

Accessing the Complete Application Code

To complement this tutorial, the complete source code of the museum booking application will be made available on GitHub. This is an excellent opportunity for you to explore the codebase, experiment with modifications, and understand the application’s structure and functionality in greater detail.

Clone the Repository
To work with the code on your local machine, clone the repository using Git:

Bash
git clone https://github.com/timerise-io/vue-museum-booking-app.git
cd vue-museum-booking-app

Explore and Modify
Once you have the code, feel free to explore and modify it. Whether you want to understand specific functionalities, add new features, or customize the UI, this codebase can serve as a practical starting point.

Conclusion

This tutorial provided a step-by-step guide on setting up a Vue.js application, integrating with a headless API like Timerise, and deploying it. The code snippets are crucial parts of the application, demonstrating how to integrate the Timerise API with a Vue.js app using Apollo Client for GraphQL.