How I built an event ticketing system with Next.js and Firebase
Tags: react, javascript, nextjs, programming
Published: 2023-05-16 07:15:18

After excessive procrastination, I decided to improve my developer portfolio by building real-world projects that stand out from the job-hunting pool. Then, I came up with an idea, an event ticketing system.

In this tutorial, I'll walk you through how I built an event ticketing system with Next.js and Firebase. 
Upon completion, you'll be able to create advanced web applications using Firebase's amazing features, like real-time database, authentication, and file storage.

> _💡 PS: This tutorial assumes you have a basic knowledge of React or Next.js._

## Application Workflow
Before we start coding, let me summarise how the application works. The application does the following:

- authenticates users via Email and Password with Firebase,
- allows users to create events and generate a registration link to share with friends or the public.
- when someone registers via your invite link, they receive the event ticket in their email, including a passcode which you can use to verify their event ticket from your dashboard.
- The application also allows you as a user to view the number of people who registered for your event, validate an attendee's ticket via the passcode they receive, and also disable the registration link when you have enough attendees. You can also delete an event after it has occurred.

![Event ticket system application workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ypf0eyamep91u7tf1xrt.png)

> _💡 Check out the [live version of the application](https://eventtiz.vercel.app)._

## The Design process 
Here, I'll walk you through creating the required pages for the web application.

First of all, you need a homepage for the application. The home page should have a link where new users can create an account and another for existing users to log into their accounts.

![Home page preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zac7qo5ywvgwr7fst25n.png)

Next, create the sign-in and sign-up page. In this article, I'll use the Email and Password authentication method.

![Login/Signup page preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ur4lettffol61zvlzzu.png)

After successful login, users can create new events, view existing events, and log out of the application on a single page, called the dashboard page.

![Dashboard page preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbkfbdy1kl8hal57otvd.png)

Next, you need to allow users to view the attendees for each event, disable registration, validate the user's ticket at the venue, and delete an event. 

Therefore,  you need to create a route for each event. You may adapt my method, where I made each event clickable redirecting users to another page containing every detail of the particular event.

![Each event page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8fx6j0vwdixtce4g4gak.png)
From the image above, users can disable the registration link for the event and view and validate the attendees' list.

Finally, create the event registration page. Before showing this page to a visitor, you need to check if the event registration link has not been disabled.

![Registration closed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xoo5ujuzr8snbkh54wzt.png)

After validation, users should supply their name and email, be added to the attendees' list, and receive an email containing the event details. 

![Tickets disbursement page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jb1j3zr0ltjxkd5zsxle.png)

Since you've learnt how to build the pages of the application. Let's code.💪🏾  

In the upcoming sections, you'll learn how to use the various features provided by Firebase.

## What is Firebase?

[Firebase](https://firebase.google.com) is a Backend-as-a-Service (Baas) owned by Google that enables developers to build full-stack web applications in a few minutes. Services like Firebase make it very easy for front-end developers to build full-stack web applications with little or no backend programming skills.

[Firebase](https://modularfirebase.web.app/common-use-cases) provides various authentication methods, a NoSQL database, a real-time database, file storage, cloud functions, hosting services, and many more.

## How to add Firebase to a Next.js application
To add Firebase to a Next.js app, follow the steps below: 

Visit the [Firebase console](https://console.firebase.google.com) and sign in with a Gmail account.  

Create a Firebase project once you are signed in.

Select the `</>` icon to create a new Firebase web app.

![Create Firebase App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4guyjuj39cav9cyyqnm.png) 

Provide the name of your app and register the app.

![Firebase Web App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ze2wblrsvzt1ux35debq.png) 

Install the Firebase SDK by running the code snippet below.

```bash
npm install firebase
``` 

Create a `firebase.js` file at the root of your Next.js project and copy the Firebase configuration code for your app into the file. 

```js
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";

const firebaseConfig = {
	apiKey: "******",
	authDomain: "**********",
	projectId: "********",
	storageBucket: "******",
	messagingSenderId: "**********",
	appId: "********",
	measurementId: "********",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
```

Finally, update the `firebase.js` to contain some required modules for Firebase Authentication, Storage, and Database.

```js
import { initializeApp, getApps } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { EmailAuthProvider } from "firebase/auth";
import { getAuth } from "firebase/auth";
import { getStorage } from "firebase/storage";

const firebaseConfig = {...<your_config>...};

// 👇🏻 Initializing Firebase in Next.js
let app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
//👇🏻 Initializing the Email/Password Auth
const provider = new EmailAuthProvider();
//👇🏻 Firebase File Storage
const storage = getStorage(app);
//👇🏻 Firebase Data Storage
const db = getFirestore(app);
//👇🏻 Firebase Auth
const auth = getAuth(app);

export { provider, auth, storage };
export default db;
```
Congratulations!🎉 You've successfully added Firebase to your Next.js app. Next, let's set up the needed Firebase features.

### Setting up Firebase Authentication
Before you can add Firebase Authentication to your application, you need to set it up on your console.

Select Build on the left-hand panel, and click Authentication.

![Firebase Authentication](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o95mfwa0n8u68kgaobdo.png) 

Click the `Get Started` button, enable the Email/Password method, and click Save.

![Enable Email and Password Auth](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5jvqag78gbu9xccwsdie.png) 

If successful, your screen should display this:
![Successfully added email and password](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyrslovy8kvqohsn6pkz.png) 

### Setting up Firebase Firestore

Select `Firestore Database` from the left-hand side menu and create a database.

Create the database in test mode, and use the default Cloud Firestore location settings.

![Create Firestore Test mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mdarn26ul8nb2g2xpsys.png)

After creating your database, select `Usage` from the top menu bar, edit the rules, and publish the changes. This enables you to make requests to the database for a longer period of time.

![Publish changes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8w2izinobm8deomok99a.png)

Congratulations!🎉 Your Firebase Database is ready.

### Setting up Firebase Storage
Select `Storage` from the left-hand side menu, and create the storage in test mode using the default location settings.

Update the timestamp for the Firebase Storage as done in the Firebase Firestore above.

![Firebase Storage Initialization](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qbnqcmmjr3dk1lew1ojm.png) 

Finally, you've set up your Firebase console. Next, I will walk you through how to communicate with Firebase by building the event ticketing system.

## Communicating with Firebase: Authenticating users

In this section, I'll walk you through the authentication aspect of the event ticketing system. 

You can create a `utils` folder containing the functions and import them into the required components.

### Signing up new users
This function is executed on the SignUp page. It accepts the user's email and password and creates an account with the credentials.

```js 
import { createUserWithEmailAndPassword } from "firebase/auth";
import { useRouter } from "next/router";
import { auth } from "./firebase";

const router = useRouter();

export const firebaseCreateUser = (email, password, router) => {
	createUserWithEmailAndPassword(auth, email, password)
		.then((userCredential) => {
			const user = userCredential.user;
			successMessage("Account created 🎉");
			router.push("/login");
		})
		.catch((error) => {
			console.error(error);
			errorMessage("Account creation declined ❌");
		});
};
``` 
The code snippet above accepts the `useRouter` hook and the user's email and password then creates an account for the user. 

With the `createUserWithEmailAndPassword` function, Firebase handles the authentication process. If successful, the user is notified and redirected to the login page; otherwise displays an error message.

### Signing in existing users

This function allows existing users to access the application. It accepts the user's email and password and returns a `user` object containing all the user's information. 

```js
import { signInWithEmailAndPassword } from "firebase/auth";
import { useRouter } from "next/router";
import { auth } from "./firebase";

const router = useRouter();

export const firebaseLoginUser = (email, password, router) => {
	signInWithEmailAndPassword(auth, email, password)
		.then((userCredential) => {
			const user = userCredential.user;
			successMessage("Authentication successful 🎉");
			router.push("/dashboard");
		})
		.catch((error) => {
			console.error(error);
			errorMessage("Incorrect Email/Password ❌");
		});
};
``` 
The code snippet above validates the user's credentials and returns an object containing all the information related to the user. If the process is successful, it redirects the user to the dashboard page; otherwise returns an error.

### Logging users out 
Firebase also provides a `signOut` function that enables users to log out of the application. 
 
Here is how it works:

```js
import { signOut } from "firebase/auth";
import { useRouter } from "next/router";
import { auth } from "./firebase";

const router = useRouter();

export const firebaseLogOut = (router) => {
	signOut(auth)
		.then(() => {
			successMessage("Logout successful! 🎉");
			router.push("/");
		})
		.catch((error) => {
			errorMessage("Couldn't sign out ❌");
		});
};
```
The code snippet above logs users out of the application by getting the active user's details and logging them out with the help of the `signOut` function. 

### Protecting pages from unauthenticated users
To do this, you can store the user's information object to a state after logging in or use the Firebase `onAuthStateChanged` hook.

Using the `onAuthStateChanged` hook: 

```js
import { onAuthStateChanged } from "firebase/auth";
import React, { useEffect, useCallback } from "react";
import { useRouter } from "next/router";
import { auth } from "./firebase";

const router = useRouter();

const isUserLoggedIn = useCallback(() => {
	onAuthStateChanged(auth, (user) => {
		if (user) {
			setUser({ email: user.email, uid: user.uid });
			//👉🏻 Perform an authenticated request
		} else {
			return router.push("/register");
		}
	});
}, []);

useEffect(() => {
	isUserLoggedIn();
}, [isUserLoggedIn]);
``` 
The `onAuthStateChanged` hook checks if the user is active and returns the object containing all the user's details. You can execute the function on page load for routes containing protected data.  

## Communicating with Firebase: Interacting with the database  and storage

In this section, you'll learn how to interact with the Firestore Database by creating, updating, and deleting events and uploading images to the Firestore Storage. 

### Creating new events
After authenticating the users, they should be able to create new events via a link on the dashboard page. Create a form field that accepts similar details, as shown below.

![Create Event](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xs0qcywxdwr9qcbh4lls.png)

```js
export const addEventToFirebase = async (
	id,
	title,
	date,
	time,
	venue,
	description,
	note,
	flier,
	router
) => {
	console.log({
		id,
		title,
		date,
		time,
		venue,
		description,
		note,
		flier,
	});
};
``` 
The function above accepts all the data related to each event. The `id` attribute in the code snippet above refers to the user's id, not the event's id. Adding the user's id to each event data enables you to query the events on the database via the id.

Since the form field in the image above accepts the event's flier via image upload. Therefore, you need to save the image to Firebase and attach it to the event on the database. 

How do you do this? Let's see 💪🏾

Create a function `handleFileReader` within the `Create Event` component, as done below. 

```js
const handleFileReader = (e) => {
	const reader = new FileReader();
	if (e.target.files[0]) {
		reader.readAsDataURL(e.target.files[0]);
	}
	reader.onload = (readerEvent) => {
		setFlier(readerEvent.target.result);
	};
};
``` 

The `handleFileReader` function takes an event parameter, uses the `FileReader` object to read the image in base64 string format then saves it into the `setFlier` state. 

Execute the `handleFileReader` function when the uploaded file changes. 

```jsx
<input
	name='flier'
	type='file'
	className='border-[1px] py-2 px-4 rounded-md mb-3'
	accept='image/*'
	onChange={handleFileReader}
/>
```  
Since you've been able to convert the file to a base64 string, next, upload the event's data to Firebase when a user clicks the `Create Event` button.

```js 
import db, { storage } from "./firebase";
import { addDoc, collection, doc, updateDoc } from "@firebase/firestore";
import { getDownloadURL, ref, uploadString } from "@firebase/storage";

export const addEventToFirebase = async (
	id,
	title,
	date,
	time,
	venue,
	description,
	note,
	flier,
	router
) => {
	const docRef = await addDoc(collection(db, "events"), {
		user_id: id,
		title,
		date,
		time,
		venue,
		description,
		note,
		slug: createSlug(title),
		attendees: [],
		disableRegistration: false,
	});

	const imageRef = ref(storage, `events/${docRef.id}/image`);

	if (flier !== null) {
		//👇🏻 User uploaded a file
	} else {
		//👇🏻 No flier uploaded
	}
};
``` 

![The Event object](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ota6flpxrxvb3h88beh8.png)

- From the code snippet above,
  - The `addDoc` function adds the event data to a newly-created Firebase collection called events.
  - `docRef` is the event document on Firebase containing the object data.
  - I added three more properties - (`slug, attendees, disableRegistration`) to the event document.
  - The `attendees` array will contain the list of people who registered for the event.
  - The `slug` enables us to generate a human-readable URL for each event.
   - The `disableRegistration` attribute enables us to differentiate events accepting registration from others.
  - The `imageRef` variable creates a reference between the uploaded flier and the event document. It establishes a one-one relationship between the image and the event document.
  - Lastly, since users can either choose to upload an event flier or not, you need to ensure that both cases are successful.

Before we proceed, create the `createSlug` function. It accepts the event title and creates a human-readable URL string format.

```js
export const createSlug = (sentence) => {
	let slug = sentence.toLowerCase().trim();
	slug = slug.replace(/[^a-z0-9]+/g, "-");
	slug = slug.replace(/^-+|-+$/g, "");
	return slug;
};
``` 

Finally, update the conditional statement within the `addEventToFirebase` function. 

```js 
//👇🏻 Database reference to the image
const imageRef = ref(storage, `events/${docRef.id}/image`);

if (flier !== null) {
	await uploadString(imageRef, flier, "data_url").then(async () => {
		//👇🏻 Gets the image URL
		const downloadURL = await getDownloadURL(imageRef);
		//👇🏻 Updates the docRef, by adding the flier URL to the document
		await updateDoc(doc(db, "events", docRef.id), {
			flier_url: downloadURL,
		});

		//Alerts the user that the process was successful
		successMessage("Event created! 🎉");
		router.push("/dashboard");
	});
} else {
	successMessage("Event created! 🎉");
	router.push("/dashboard");
}
``` 
The conditional statement checks if the user uploaded an image. If so, it uploads the image to Firebase Storage and updates the event document with a new property called `flier_url`. 

The `flier_url` property contains the image URL hosted on Firebase Storage. If there is no image, none of this process is required, and the user gets notified that the event has been created.

### Getting all the events created by a user

On the Dashboard page, you'll need to display all the events created by the current user. 

To do this, query the `events` collection on Firebase and return only events whose `user_id` attribute matches the current user's ID.

```js 
import { collection, doc, onSnapshot, query, where } from "@firebase/firestore";
import db from "./firebase";

export const getEvents = (id, setEvents) => {
	try {
		const q = query(collection(db, "events"), where("user_id", "==", id));

		const unsubscribe = onSnapshot(q, (querySnapshot) => {
			const firebaseEvents = [];
			querySnapshot.forEach((doc) => {
				firebaseEvents.push({ data: doc.data(), id: doc.id });
			});
			setEvents(firebaseEvents);

			return () => unsubscribe();
		});
	} catch (error) {
		console.error(error);
	}
};
``` 

The code snippet above accepts the user's id, filters the events via the id, and returns only events whose `user_id` attribute matches the current user.  

### Deleting events 
Allowing users to delete an event is one of the application's features. The function below shows how you can achieve that: 

```js 
import { doc, deleteDoc } from "@firebase/firestore";
import { ref, deleteObject } from "@firebase/storage";
import db, { storage } from "./firebase";

export const deleteEvent = async (id) => {
	await deleteDoc(doc(db, "events", id));

	const imageRef = ref(storage, `events/${id}/image`);
	deleteObject(imageRef)
		.then(() => {
			console.log("Deleted successfully");
		})
		.catch((error) => {
			console.error("Image does not exist");
		});
};
``` 
The `deleteDoc` function deletes an event via its ID, and the `deleteObject` deletes the flier attached to the event using the `imageRef`.

### Disabling new registrations
The application is incomplete if the user who created an event cannot disable registration for the event when the registration deadline has passed or if they have enough guests (attendees). 

```js
import { doc, updateDoc } from "@firebase/firestore";
import db from "./firebase";

export const updateRegLink = async (id) => {
	const number = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
	const eventRef = doc(db, "events", id);
	updateDoc(eventRef, {
		disableRegistration: true,
	});
};
``` 
The code snippet above accepts the event's id, generates a random number, and updates the event document by setting the `disableRegistration` property to `true`. 

When users receive the invite to an event with its `disableRegistration` property set to `true`, the page does not display a registration form; instead, it shows that registration for that event is closed. 

### Handling attendees registration
Before you add an attendee to an event, you have to do three things:

1. Fetch the event document via its ID attached to the invite link.
2. Validate the attendee's email with the existing array of attendees at the event.
3. If the email does not exist on the list, add the user to the list. Otherwise, return an error showing that the user has already registered for the event.

```js
import { getDoc, doc, updateDoc, arrayUnion } from "@firebase/firestore";

//👇🏻 generates a unique passcode for the attendee
export const generatePasscode = () =>
	Math.random().toString(36).substring(2, 10);

export const registerAttendee = async (name, email, event_id) => {
	const passcode = generatePasscode();
	const eventRef = doc(db, "events", event_id);
	const eventSnap = await getDoc(eventRef);
	let firebaseEvent = {};
	if (eventSnap.exists()) {
		firebaseEvent = eventSnap.data();
		//👇🏻 gets the attendees' list
		const attendees = firebaseEvent.attendees;
		//👇🏻 filter the list
		const result = attendees.filter((item) => item.email === email);
		//👇🏻 if registration is valid
		if (result.length === 0 && firebaseEvent.disableRegistration === false) {
			//👇🏻 adds the attendee to the list
			await updateDoc(eventRef, {
				attendees: arrayUnion({
					name,
					email,
					passcode,
				}),
			});
			// 👉🏻 sendEventTicketViaEmail()
			successMessage("User registered successfully! ✅");
		} else {
			errorMessage("User already registered ❌");
		}
	}
};
``` 

> 💡 The `arrayUnion` function provided by Firebase enables us to push an item to an array attribute on a document. 

The code snippet above fetches an event via its ID, gets its attendees' list, and checks whether the user hasn't registered for the event before adding the user to the list of attendees. 

The `generatePasscode` function generates a random sequence of unique numbers assigned to every user for validating each user's ticket on the event creator's dashboard. 

On the event details page, you can show the attendees' list for the event and an input field that searches for attendees via their passcode. 

## Creating and sending event tickets to attendees
After an attendee has successfully registered for an event, you need to send an email containing the event details and their passcode to the event. 

To do this, you can use a JavaScript library that supports email notifications, such as [EmailJS](https://www.emailjs.com), [Novu](https://novu.co), and [Sendgrid](https://sendgrid.com).

### How to send emails via EmailJS
Here, I'll guide you through how you can add EmailJS to the application.

Install EmailJS to the Next.js application by running the code below:

```bash
npm install @emailjs/browser
```

[Create an EmailJS account](https://dashboard.emailjs.com/sign-up) and add an email service provider to your account.

Add an email template as done in the image below. The words in curly brackets represent variables that can hold dynamic data.

![email template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yufrbnsgph3yhbvy9mf.png)

```txt
Dear {{name}},

We hope this message finds you bursting with excitement because you are about to embark on a journey like no other! We are thrilled to present your personal ticket details for the most incredible event of the year!

Event Name: {{title}}

Event Description: {{description}}

Time: {{time}}

Date: {{date}} (Save the date)

PS: {{note}}


Please keep your invitation code secret - {{passcode}}. This shows you're part of the attendees.

DOWNLOAD EVENT FLIER - {{flier_url}}
```

Congratulations! You can send the event tickets using the email template above. Feel free to beautify yours.🔥

### Sending the event tickets to attendees with EmailJS
To send the event ticket to the newly registered attendee, update the `if` block within the `registerAttendee` function, as done below. 

```js 
if (result.length === 0) {
	await updateDoc(eventRef, {
		attendees: arrayUnion({
			name,
			email,
			passcode,
		}),
	});
	const flierURL = firebaseEvent.flier_url
		? firebaseEvent.flier_url
		: "No flier for this event";
	sendEmail(
		name,
		email,
		firebaseEvent.title,
		firebaseEvent.time,
		firebaseEvent.date,
		firebaseEvent.note,
		firebaseEvent.description,
		passcode,
		flierURL,
		setSuccess,
		setLoading
	);
} else {
	setLoading(false);
	errorMessage("User already registered ❌");
}
```
The code snippet checks if there is no existing user with the same email before adding the new user to the attendees' list. If there is no flier attached to the event, it takes note of that and passes all the event's information as parameters to the `sendEmail` function.

Create the `sendEmail` function to send the event details to the newly-registered attendee's email.

```js
import emailjs from "@emailjs/browser";

const sendEmail = (
	name,
	email,
	title,
	time,
	date,
	note,
	description,
	passcode,
	flier_url,
	setSuccess,
	setLoading
) => {
	emailjs
		.send(
			process.env.NEXT_PUBLIC_SERVICE_ID,
			process.env.NEXT_PUBLIC_TEMPLATE_ID,
			{
				name,
				email,
				title,
				time,
				date,
				note,
				description,
				passcode,
				flier_url,
			},
			process.env.NEXT_PUBLIC_API_KEY
		)
		.then(
			(result) => {
				setSuccess(true);
				//👉🏻 Email sent ✅
			},
			(error) => {
				alert(error.text);
			}
		);
};
```
The `sendEmail` function accepts all the required parameters and sends the email using the [EmailJS library](https://www.emailjs.com/docs/examples/reactjs).

## Conclusion
Congratulations on making it thus far! You've learnt

- what Firebase is,
- how to add Firebase to a Next.js app,
- how to work with Firebase Auth, Storage, and Database, and
- how to build an event ticketing system.

I've built a live version of the application, check it out - [https://eventtiz.vercel.app](https://eventtiz.vercel.app). The source code is also available [here](https://github.com/dha-stix/eventtiz)

Firebase is a great tool that provides almost everything you need to build a full-stack web application. If you want to create a full-stack web application without any backend programming experience, consider using Firebase.

Thank you for reading! 🎉

### Open to work🙂
Did you enjoy this article or need an experienced Technical Writer / React Developer for a remote, full-time, or contract-based role? Feel free to contact me.
[GitHub](https://github.com/dha-stix) ||  [LinkedIn](https://www.linkedin.com/in/david-asaolu234/) ||  [Twitter](https://twitter.com/dayvid_JS)

[![Buy David a coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cinwy8wfhl6ra51t6ml5.png)](https://www.buymeacoffee.com/asaoludavid)
<img src="https://media.giphy.com/media/fVtcfEXWQJQUbsF1sH/giphy.gif" alt="Thank You" width="auto" height="auto"/>