- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
TL;DR
In this tutorial, you will learn how to build a therapy marketplace app using Next.js, Stream, and Firebase. The app will allow clients to find therapists, chat, and book virtual therapy sessions.
Firebase will handle the backend operation, while Stream will handle in-app chat and video calls.
App Overview
The application supports two types of users: therapists and clients.
Therapists can:
? Create an account based on their area of specialization and set an hourly rate.
? Manage and confirm booking payments.
? Initiate chats with clients and schedule virtual video sessions.
? Receive reviews from clients after each session.
Clients can:
?? Browse and book therapists based on their specialization and specific needs.
?? Chat with therapists once a booking is confirmed.
?? Attend 1-on-1 virtual sessions with therapists.
?? Leave reviews on therapists’ profiles.
Prerequisites
To fully understand this tutorial, you need to have a basic understanding of React or Next.js.
We will use the following tools:
Create a Next.js project by running the following code snippet:
npx create-next-app therapy-app
Install the package dependencies for the project:
npm install firebase @stream-io/node-sdk @stream-io/video-react-sdk stream-chat stream-chat-react
To install the Shadcn UI library, .
Once everything is set up, your Next.js project is ready.
Now, let's start building! ?
How to Set Up Firebase for Auth and DB Interactions
Firebase is a cloud platform that enables you to build full-stack software applications without worrying about managing your database or server infrastructure. It provides features like authentication, real-time database, cloud functions, file storage, and more.
In this section, you'll learn how to install Firebase in a Next.js application and configure the Firestore Database, Firebase storage, and authentication within your Next.js application.
Setting up Firebase in a Next.js Application
Install the Firebase Node.js package by running the code snippet below:
npm install firebase
Open the in your browser and create a new Firebase project.
Within the project dashboard, click the web icon </> to add a Firebase app to the project.
Register the app by entering a nickname, then copy the auto-generated Firebase configuration code.
Create a lib/firebase.ts file within the Next.js src folder and paste the following code snippet into the file:
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 Firebase app configuration code
};
//?? Initialize Firebase
const app =
getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
const provider = new EmailAuthProvider();
const storage = getStorage(app);
const db = getFirestore(app);
const auth = getAuth(app);
//?? exports each variable for use within the application
export { provider, auth, storage };
export default db;
The code snippet above initializes Firebase Storage, Firestore, and the Email Authentication provider. This setup allows you to add file storage, interact with a database, and implement email/password authentication within the application.
Before interacting with Firebase features, you must set them up in your project dashboard.
Click Build in your dashboard's sidebar navigation. This will open a dropdown menu with various features you can enable for your project.
Select Authentication, Firestore Database, and Storage from the drop-down and add them to the project.
Congratulations! You can now start interacting with these Firebase features in your Next.js project.
Authenticating Client Users
In this section, you will learn how to handle the authentication process for client users using Firebase.
Add a lib/auth-functions.ts file to the Next.js project and copy the following code snippet into the file:
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
} from "firebase/auth";
import { getDownloadURL, ref, uploadBytes } from "@firebase/storage";
import db, { auth, storage } from "./firebase";
import { doc, setDoc, getDoc } from "firebase/firestore";
export const clientSignUp = async (form: FormData) => {
//?? Get form data
const { name, email, password } = {
name: form.get("name") as string,
email: form.get("email") as string,
password: form.get("password") as string,
};
try {
//?? Create user using the email and password
const { user } = await createUserWithEmailAndPassword(
auth,
email,
password
);
// ?? Check if user has been created
if (!user) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to create user",
};
}
// ?? Create a document in the Firebase Firestore
const docRef = doc(db, "clients", user.uid);
await setDoc(docRef, {
name,
email,
});
// ?? Return user data
return {
code: "auth/success",
status: 201,
user,
message: "Acount created successfully! ?",
};
} catch (err) {
return {
code: "auth/failed",
status: 500,
user: null,
err,
message: "Failed to create user",
};
The clientSignUp function accepts the form data, creates an account using the email and password, and adds a document containing the user's email, ID, and name to Firebase Firestore. Execute the function when the user submits the sign-up form.
Add the following code snippet to the auth-functions.ts file to sign client users into the application.
export const clientLogin = async (form: FormData) => {
// ?? Get form data
const email = form.get("email") as string;
const password = form.get("password") as string;
try {
// ?? Sign in using the email and password
const { user } = await signInWithEmailAndPassword(auth, email, password);
// ?? Return error if credentials are incorrect
if (!user) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to login",
};
}
// ?? Retrieve user data from Firebase Firestore
const docSnap = await getDoc(doc(db, "clients", user.uid));
// ?? Return error if document doesn't exist
if (!docSnap.exists()) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "User is Not a Client",
};
}
// ?? Return data
return {
code: "auth/success",
status: 200,
user,
message: "Login successful",
};
} catch (err) {
return {
code: "auth/failed",
status: 500,
user: null,
err,
message: "Failed to login",
};
}
};
The clientLogin function takes the user's email and password from the form and signs them into the application. It then checks if the user has a document in the Firebase clients collection to confirm that they are a client user and not a therapist before allowing access to client-only pages.
Finally, add a sign out function to the file to allow users to log out of the application when needed.
export const authLogout = async () => {
try {
await auth.signOut();
return { code: "auth/success", status: 200, message: "Logout successful" };
} catch (err) {
return {
code: "auth/failed",
status: 500,
message: "Failed to logout",
err,
};
}
};
Authenticating Therapists
Here, you’ll learn about the necessary attributes for the therapists and how they can create an account and sign in to the application.
Add the code snippet below to the auth-functions.ts file:
export const therapistSignUp = async (form: FormData) => {
const userData = {
name: form.get("name") as string,
email: form.get("email") as string,
password: form.get("password") as string,
qualification: form.get("qualification") as string,
summary: form.get("summary") as string,
specialization: form.get("specialization") as string,
country: form.get("country") as string,
image: form.get("image") as File,
};
//?? -- Next steps --
// 1. create an account using the email and password.
// 2. upload the image to Firebase storage.
// 3. retrieve the image download URL.
// 4. create a Firebase document with all the attributes.
};
Modify the therapistSignUp function to create a user using the email and password, upload the therapist's image, and save all the form data to the Firebase therapists collection.
export const therapistSignUp = async (form: FormData) => {
// ..?? form data placeholder
try {
// ?? create an account
const { user } = await createUserWithEmailAndPassword(
auth,
userData.email,
userData.password
);
// ?? if error return
if (!user) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to create user",
};
}
// ?? upload image
const imageRef = ref(storage, `therapists/${user.uid}/image`);
await uploadBytes(imageRef, userData.image).then(async () => {
// ?? get image download URL
const downloadURL = await getDownloadURL(imageRef);
if (!downloadURL) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to upload image",
};
}
// ?? create a Firebase document using the user attributes
const docRef = doc(db, "therapists", user.uid);
await setDoc(docRef, {
name: userData.name,
email: userData.email,
specialization: userData.specialization,
qualification: userData.qualification,
summary: userData.summary,
country: userData.country,
image: downloadURL,
});
// ?? return success message
return {
code: "auth/success",
status: 201,
user: userData,
message: "Acount created successfully! ?",
};
} catch (err) {
return {
code: "auth/failed",
status: 500,
user: null,
err,
message: "Failed to create user",
};
}
};
Finally, add a therapistsLogin function to the file that takes the user's email and password from the form and signs them into the application. It also checks if the user has a document in the Firebase therapists collection before granting access to the dashboard page.
export const therapistLogin = async (form: FormData) => {
const email = form.get("email") as string;
const password = form.get("password") as string;
try {
const { user } = await signInWithEmailAndPassword(auth, email, password);
if (!user) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to login",
};
}
const docSnap = await getDoc(doc(db, "therapists", user.uid));
if (!docSnap.exists()) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "User is Not a Therapist",
};
}
return {
code: "auth/success",
status: 200,
user,
message: "Login successful",
};
} catch (err) {
return {
code: "auth/failed",
status: 500,
user: null,
err,
message: "Failed to login",
};
}
};
Installing the Stream Chat Firebase Extension
The automatically connects your Firebase users to Stream Chat, making it easy for Stream to access and manage your users.
Before we proceed, and a that holds all your apps.
Add a new app to the organization and copy the Stream API and Secret key into the .env.local file.
NEXT_PUBLIC_STREAM_API_KEY=<paste_from_Stream_app_dashboard>
STREAM_SECRET_KEY=<paste_from_Stream_app_dashboard>
Next, visit the and select the Firebase project where you want to install the extension.
Note: Before installing the extension, you must upgrade your Firebase project to the Blaze (pay-as-you-go) plan.
During installation, enable Artifact Registry, Cloud Functions, and Secret Manager. These permissions allow Stream to access and manage your Firebase users.
Finally, enter your Stream API key and secret in the configuration fields, then click the Install Extension button.
The extension will be installed within a few minutes. Once setup is complete, every new Firebase user will automatically be added to Stream.
The Application Database Design
In this section, you'll learn how to create the required Firestore collections and perform CRUD operations within the application.
Beyond the authentication pages, the app has four key pages:
The table below outlines the attributes of the Firestore collections:
In this tutorial, you will learn how to build a therapy marketplace app using Next.js, Stream, and Firebase. The app will allow clients to find therapists, chat, and book virtual therapy sessions.
Firebase will handle the backend operation, while Stream will handle in-app chat and video calls.
App Overview
The application supports two types of users: therapists and clients.
Therapists can:
? Create an account based on their area of specialization and set an hourly rate.
? Manage and confirm booking payments.
? Initiate chats with clients and schedule virtual video sessions.
? Receive reviews from clients after each session.
Clients can:
?? Browse and book therapists based on their specialization and specific needs.
?? Chat with therapists once a booking is confirmed.
?? Attend 1-on-1 virtual sessions with therapists.
?? Leave reviews on therapists’ profiles.
Prerequisites
To fully understand this tutorial, you need to have a basic understanding of React or Next.js.
We will use the following tools:
- - a Backend-as-a-service platform developed by Google to enable us to add authentication, database, real-time communication, file storage, cloud functions, and many others within software applications.
- - a Firebase extension that automatically connects your Firebase users to Stream.
- and - enables real-time chat and video/audio communication in your application.
- : a UI component library that provides customizable, beautifully designed, and accessible UI components for your applications.
Create a Next.js project by running the following code snippet:
npx create-next-app therapy-app
Install the package dependencies for the project:
npm install firebase @stream-io/node-sdk @stream-io/video-react-sdk stream-chat stream-chat-react
To install the Shadcn UI library, .
Once everything is set up, your Next.js project is ready.
Now, let's start building! ?
How to Set Up Firebase for Auth and DB Interactions
Firebase is a cloud platform that enables you to build full-stack software applications without worrying about managing your database or server infrastructure. It provides features like authentication, real-time database, cloud functions, file storage, and more.
In this section, you'll learn how to install Firebase in a Next.js application and configure the Firestore Database, Firebase storage, and authentication within your Next.js application.
Setting up Firebase in a Next.js Application
Install the Firebase Node.js package by running the code snippet below:
npm install firebase
Open the in your browser and create a new Firebase project.
Within the project dashboard, click the web icon </> to add a Firebase app to the project.
Register the app by entering a nickname, then copy the auto-generated Firebase configuration code.
Create a lib/firebase.ts file within the Next.js src folder and paste the following code snippet into the file:
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 Firebase app configuration code
};
//?? Initialize Firebase
const app =
getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
const provider = new EmailAuthProvider();
const storage = getStorage(app);
const db = getFirestore(app);
const auth = getAuth(app);
//?? exports each variable for use within the application
export { provider, auth, storage };
export default db;
The code snippet above initializes Firebase Storage, Firestore, and the Email Authentication provider. This setup allows you to add file storage, interact with a database, and implement email/password authentication within the application.
Before interacting with Firebase features, you must set them up in your project dashboard.
Click Build in your dashboard's sidebar navigation. This will open a dropdown menu with various features you can enable for your project.
Select Authentication, Firestore Database, and Storage from the drop-down and add them to the project.
Congratulations! You can now start interacting with these Firebase features in your Next.js project.
Authenticating Client Users
In this section, you will learn how to handle the authentication process for client users using Firebase.
Add a lib/auth-functions.ts file to the Next.js project and copy the following code snippet into the file:
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
} from "firebase/auth";
import { getDownloadURL, ref, uploadBytes } from "@firebase/storage";
import db, { auth, storage } from "./firebase";
import { doc, setDoc, getDoc } from "firebase/firestore";
export const clientSignUp = async (form: FormData) => {
//?? Get form data
const { name, email, password } = {
name: form.get("name") as string,
email: form.get("email") as string,
password: form.get("password") as string,
};
try {
//?? Create user using the email and password
const { user } = await createUserWithEmailAndPassword(
auth,
email,
password
);
// ?? Check if user has been created
if (!user) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to create user",
};
}
// ?? Create a document in the Firebase Firestore
const docRef = doc(db, "clients", user.uid);
await setDoc(docRef, {
name,
email,
});
// ?? Return user data
return {
code: "auth/success",
status: 201,
user,
message: "Acount created successfully! ?",
};
} catch (err) {
return {
code: "auth/failed",
status: 500,
user: null,
err,
message: "Failed to create user",
};
The clientSignUp function accepts the form data, creates an account using the email and password, and adds a document containing the user's email, ID, and name to Firebase Firestore. Execute the function when the user submits the sign-up form.
Add the following code snippet to the auth-functions.ts file to sign client users into the application.
export const clientLogin = async (form: FormData) => {
// ?? Get form data
const email = form.get("email") as string;
const password = form.get("password") as string;
try {
// ?? Sign in using the email and password
const { user } = await signInWithEmailAndPassword(auth, email, password);
// ?? Return error if credentials are incorrect
if (!user) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to login",
};
}
// ?? Retrieve user data from Firebase Firestore
const docSnap = await getDoc(doc(db, "clients", user.uid));
// ?? Return error if document doesn't exist
if (!docSnap.exists()) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "User is Not a Client",
};
}
// ?? Return data
return {
code: "auth/success",
status: 200,
user,
message: "Login successful",
};
} catch (err) {
return {
code: "auth/failed",
status: 500,
user: null,
err,
message: "Failed to login",
};
}
};
The clientLogin function takes the user's email and password from the form and signs them into the application. It then checks if the user has a document in the Firebase clients collection to confirm that they are a client user and not a therapist before allowing access to client-only pages.
Finally, add a sign out function to the file to allow users to log out of the application when needed.
export const authLogout = async () => {
try {
await auth.signOut();
return { code: "auth/success", status: 200, message: "Logout successful" };
} catch (err) {
return {
code: "auth/failed",
status: 500,
message: "Failed to logout",
err,
};
}
};
Authenticating Therapists
Here, you’ll learn about the necessary attributes for the therapists and how they can create an account and sign in to the application.
Add the code snippet below to the auth-functions.ts file:
export const therapistSignUp = async (form: FormData) => {
const userData = {
name: form.get("name") as string,
email: form.get("email") as string,
password: form.get("password") as string,
qualification: form.get("qualification") as string,
summary: form.get("summary") as string,
specialization: form.get("specialization") as string,
country: form.get("country") as string,
image: form.get("image") as File,
};
//?? -- Next steps --
// 1. create an account using the email and password.
// 2. upload the image to Firebase storage.
// 3. retrieve the image download URL.
// 4. create a Firebase document with all the attributes.
};
Modify the therapistSignUp function to create a user using the email and password, upload the therapist's image, and save all the form data to the Firebase therapists collection.
export const therapistSignUp = async (form: FormData) => {
// ..?? form data placeholder
try {
// ?? create an account
const { user } = await createUserWithEmailAndPassword(
auth,
userData.email,
userData.password
);
// ?? if error return
if (!user) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to create user",
};
}
// ?? upload image
const imageRef = ref(storage, `therapists/${user.uid}/image`);
await uploadBytes(imageRef, userData.image).then(async () => {
// ?? get image download URL
const downloadURL = await getDownloadURL(imageRef);
if (!downloadURL) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to upload image",
};
}
// ?? create a Firebase document using the user attributes
const docRef = doc(db, "therapists", user.uid);
await setDoc(docRef, {
name: userData.name,
email: userData.email,
specialization: userData.specialization,
qualification: userData.qualification,
summary: userData.summary,
country: userData.country,
image: downloadURL,
});
// ?? return success message
return {
code: "auth/success",
status: 201,
user: userData,
message: "Acount created successfully! ?",
};
} catch (err) {
return {
code: "auth/failed",
status: 500,
user: null,
err,
message: "Failed to create user",
};
}
};
Finally, add a therapistsLogin function to the file that takes the user's email and password from the form and signs them into the application. It also checks if the user has a document in the Firebase therapists collection before granting access to the dashboard page.
export const therapistLogin = async (form: FormData) => {
const email = form.get("email") as string;
const password = form.get("password") as string;
try {
const { user } = await signInWithEmailAndPassword(auth, email, password);
if (!user) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "Failed to login",
};
}
const docSnap = await getDoc(doc(db, "therapists", user.uid));
if (!docSnap.exists()) {
return {
code: "auth/failed",
status: 500,
user: null,
message: "User is Not a Therapist",
};
}
return {
code: "auth/success",
status: 200,
user,
message: "Login successful",
};
} catch (err) {
return {
code: "auth/failed",
status: 500,
user: null,
err,
message: "Failed to login",
};
}
};
Installing the Stream Chat Firebase Extension
The automatically connects your Firebase users to Stream Chat, making it easy for Stream to access and manage your users.
Before we proceed, and a that holds all your apps.
Add a new app to the organization and copy the Stream API and Secret key into the .env.local file.
NEXT_PUBLIC_STREAM_API_KEY=<paste_from_Stream_app_dashboard>
STREAM_SECRET_KEY=<paste_from_Stream_app_dashboard>
Next, visit the and select the Firebase project where you want to install the extension.
Note: Before installing the extension, you must upgrade your Firebase project to the Blaze (pay-as-you-go) plan.
During installation, enable Artifact Registry, Cloud Functions, and Secret Manager. These permissions allow Stream to access and manage your Firebase users.
Finally, enter your Stream API key and secret in the configuration fields, then click the Install Extension button.
The extension will be installed within a few minutes. Once setup is complete, every new Firebase user will automatically be added to Stream.
The Application Database Design
In this section, you'll learn how to create the required Firestore collections and perform CRUD operations within the application.
Beyond the authentication pages, the app has four key pages:
- /clients/dashboard – displays pending payment confirmations, open chats with therapists, and upcoming video call sessions.
- /therapists – lists all available therapists and allows clients to filter them based on specialization.
- /therapists/dashboard – shows pending payments, lets therapists approve or reject them, and displays open chats and upcoming video sessions.
- /therapists/profile/[id] – renders the therapist details using their ID and allows clients to book a session and leave a review.
The table below outlines the attributes of the Firestore collections:
| clients | therapists | pending_payments | reviews |
|---|---|---|---|
| user_id | user_id | id | client_id |
| client_id | client_name | ||
| name | name | client_name | review |
| country | therapist_id | therapist_id |