Back

How To Build An Event Ticketing System with Astro and Directus

Dennis Campos
Dennis CamposFriday, June 7, 2024
How To Build An Event Ticketing System with Astro and Directus

Astro is excellent for content-driven websites, and pairing it with your favorite CMS creates the perfect combination.

Directus offers a cloud-based service, Directus Cloud, as well as the option to run Directus locally. This guide will focus on setting it up locally, but you can also choose to set it up on the cloud if you prefer.

We will create a super simple event ticketing system with the following model:

Prerequisites

  • Node.js ≥ 20
  • Some knowledge of the Astro framework

Set up Astro

Run the following command to initialize an Astro project

npm create astro@latest

Install Directus SDK

npm install @directus/sdk directus

I'm using Tailwind, but feel free to use what you're comfortable with or even skip CSS entirely.

npx astro add tailwind

Create a .env file. We will use SQLite, but you can choose the database you are most comfortable with. While Directus reads the .env file by default, you can choose to read a config file instead. For more options, please refer to the documentation.

This serves as a good starting point for our .env file.

KEY="your_key" SECRET="superSecret" DB_CLIENT="sqlite3" DB_FILENAME="./sqlite.db" ADMIN_EMAIL="admin@echobind.com" ADMIN_PASSWORD="test1234" DIRECTUS_URL="http://localhost:8055"

If an admin email or password is not provided, Directus will supply default credentials, which will be displayed in the terminal.

Set up Directus

Once your db and your config file is set up, run the following command:

npx directus bootstrap

Now you should be able to run Directus locally:

npx directus start

Visit http://localhost:8055 and log in with your credentials specified in the .env.

If you are unfamiliar with Directus, feel free to explore before continuing.

Creating our models

Refer to the model image displayed at the beginning of this article for assistance in creating the model and its fields.

To create our models:

  • Click settings on the left side
  • Select data model
  • Create collections on the top right
  • Name it events and click next.

I'm opting for the following fields provided by Directus: date_created, date_updated, and status. You may add or omit these options according to your needs. It won't affect the instructions in this guide.

Now that we have successfully created our model, let's proceed to create our fields. You can refer back to the model image to add the necessary fields. For illustration purposes, I will guide you through the creation of one field. The process will be similar for the remaining fields.

  • Create Field
  • Select Input
  • Under key input title and a type of string
  • Select require value to be set on creation
  • Save

Now that you know how to add a field, please continue adding the remaining fields according to the model image provided earlier.

Noting the tickets model, we will add a many-to-one relationship. This indicates that many tickets can belong to a single event.

Here is how it should look.

Important - To read, create, update and perform other tasks, we must provide access control. Otherwise, an error will occur in your application.

Access Control

  • Go to settings
  • Access Control
  • Select Public
  • Click on the read access icon for the collections. It should change to a checkmark.

In our app, we are not creating or updating anything because we are handling these tasks through Directus. However, feel free to do so if you plan on expanding beyond this guide.

Set up types and Directus client

Head back into our app and create a file name directus.ts under src/lib

Let's define our types based on our image model. Here is what it should look like:

export type Event = { id: number; title: string; description: string; dateOfEvent: Date; location: string; }; export type Ticket = { id: number; eventId: Event["id"]; type: string; price: number; quantity: number; }; export type Schema = { events: Event[]; tickets: Ticket[]; };

We will use rest. Alternatively, you can opt for GraphQL if you prefer that.

import { createDirectus, rest } from "@directus/sdk"; export const client = createDirectus<Schema>(import.meta.env.DIRECTUS_URL).with( rest() );

This is how the entire file should look.

// src/lib/directus.ts import { createDirectus, rest } from "@directus/sdk"; export type Event = { id: number; title: string; description: string; dateOfEvent: Date; location: string; }; export type Ticket = { id: number; eventId: Event["id"]; type: string; price: number; quantity: number; }; export type Schema = { events: Event[]; tickets: Ticket[]; }; export const client = createDirectus<Schema>(import.meta.env.DIRECTUS_URL).with( rest() );

Add data in Directus

Head back to Directus and let's add some data.

I had ChatGPT generate some events for me. Here is the JSON file in case you want to copy and paste http://jsonblob.com/1247570720585539584 or create your own! And here is the data for tickets: http://jsonblob.com/1247582973435174912

Note: You will want to attach the eventId to an event from your event data. The ID displayed in the link above is probably not going to match what you have.

Display data in Astro

It is time to display our added events from Directus.

Head to our index file src/pages/index.astro and fetch our data using Directus sdk

// src/pages/index.astro --- import { readItems } from "@directus/sdk"; import { client } from "../lib/directus"; import { formatDateToLocale } from "../utils"; import Layout from "../layouts/Layout.astro"; const events = await client.request( readItems("events", { fields: ["id", "title", "description", "dateOfEvent", "location"], }) ); ---

Our markup

// src/pages/index.astro <Layout title="Welcome to Astro."> <div class="pt-10 pb-20 px-10"> <h2 class="text-6xl font-bold mb-4">Events</h2> <div class="space-y-10"> { events.map((event) => ( <div class="flex w-[700px] shadow-lg rounded-lg"> <div class="flex flex-col justify-center items-center p-4 bg-zinc-200"> <p class="font-bold text-3xl"> {formatDateToLocale(event.dateOfEvent).month} </p> <p class="font-bold text-4xl"> {formatDateToLocale(event.dateOfEvent).day} </p> <p class="font-bold text-3xl"> {formatDateToLocale(event.dateOfEvent).time} </p> </div> <div class="flex flex-col bg-zinc-50 p-4 space-y-7 w-full"> <h2 class="text-3xl font-bold">{event.title}</h2> <p>{event.description}</p> <div class="flex justify-between"> <p class="font-bold">{event.location}</p> <a href={`/event/${event.id}`} class="text-blue-500"> Learn More </a> </div> </div> </div> )) } </div> </div> </Layout>

In a separate terminal, launch the development server. Make sure the Directus server is also running.

Note: The formatDateToLocale function above accepts a Date object and formats it into month, day, and time values as illustrated below. Feel free to create your own version.

The outcome:

Display a single event

We can generate data based on an event using the parameters and Astro's getStaticPaths() function. More information is available here.

Create a file name index.astro under src/pages/event/[id] and add the following:

// src/pages/event/[id]/index.astro --- import { readItems } from "@directus/sdk"; import { client, type Ticket } from "../../../lib/directus"; import { formatDateToLocale } from "../../../utils"; import Layout from "../../../layouts/Layout.astro"; export async function getStaticPaths() { const events = await client.request(readItems("events")); const tickets = await client.request(readItems("tickets")); return events.map((event) => { const eventTicket = tickets.find((ticket) => ticket.eventId === event.id); return { params: { id: event.id.toString(), }, props: { event, ticket: eventTicket as Ticket, }, }; }); } const { title, description, dateOfEvent, location } = Astro.props.event; const { type, price } = Astro.props.ticket; ---

Our markup

// src/pages/event/[id] <Layout title="Event"> <div class="max-w-4xl mx-auto bg-white p-8 rounded-lg shadow-md my-10"> <h1 class="text-2xl font-bold mb-4">{title}</h1> <div class="event-details mb-6"> <p class="mb-2"> <strong>Description:</strong> {description} </p> <p class="mb-2"> <strong>Date and Time: </strong> <span>{formatDateToLocale(dateOfEvent).day}</span> <span>{formatDateToLocale(dateOfEvent).month}</span> <span>{formatDateToLocale(dateOfEvent).time}</span> </p> <p class="mb-2"><strong>Location:</strong> {location}</p> </div> <div class="ticket-info mb-6"> <h2 class="text-xl font-semibold mb-2">Tickets</h2> <p class="mb-2"> <strong>Type:</strong> {type ? type : "General Admission"} </p> <p class="mb-2"><strong>Price:</strong> ${price ? price : "150"}</p> <div class="mb-4"> <label for="quantity" class="block mb-1" ><strong>Quantity:</strong></label > <input type="number" id="quantity" class="border border-gray-300 p-2 rounded w-full max-w-xs" value="1" min="1" max="10" /> </div> </div> <a href="#" class="purchase-button inline-block bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600" >Purchase Ticket</a > </div> </Layout>

Outcome

Summary

In this guide, we built a simple event ticketing system using Astro and Directus. With Directus set up, we fetched and displayed event data in Astro, and even created dynamic routes for individual event pages. This project highlights how Astro and Directus can work together to create a powerful, content-driven event management system.

There are so many ways this can be improved and expanded. Whether you are building a simple event site or a full-featured application, this guide gives you the tools to get started and plenty of room to grow.

Dennis Campos is a software engineer here at Echobind. Want to work with us? Reach out at hi@echobind.com anytime.

Share this post

twitterfacebooklinkedin

Related Posts:

Interested in working with us?

Give us some details about your project, and our team will be in touch with how we can help.

Get in Touch