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 ofstring
- 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.