In this post, we'll be building (and deploying) a scalable, full-stack chat application using Firebase, NextJS, and ChatEngine.io!
- Firebase is a Backend as a Service (BaaS) plaform which is made by Google.
- NextJS is a Server Side Rendering Framework (SSRF) in React JS
- ChatEngine.io provides chat APIs and components for building chat apps easily.
Here, we will connect the three to make the amazing chat app above - in about 15 minutes of work!
Taking a step back, let's make the folder we'll run this project inside:
mkdir chat-firebase-next
cd chat-firebase-next
code .To setup a firebase project, make sure you have the Firebase CLI installed and a Firebase account.
Now, open a terminal in this new folder, and start a firebase project with firebase init command. Make sure you pick the following options below:
- Select / enable
functions - Select "Create a new project"
- I named my project and ID "fullstack-chat-server"
- I used Javascript, but TypeScript works too
Now, when you run a free command you should see the following:
.
├── .firebaserc
├── .gitignore
├── firebase.json
└── functions/I'll list each item below:
.firebaserclinks this folder to your new Firebase project.gitignoreignores certain files and folders for your repofirebase.jsonhosts settings for your local Firebase projectfunctionsis where we'll be writing our backend code ;)
On that note, let's setup an Express JS server in Cloud Functions.
We'll be connect all our Users to Chat Engine so they can use Chat Engine's chat infrastructure. The Chat Engine backend will handle all our web-sockets, chat storage, and everything else.
We need to make Rest API calls to Chat Engine whenever a new user Signs Up, and delete their account whenever they leave.
To start, hop in functions to install axios and hop back out:
cd functions
npm i axios
cd ..The dependency axios will make our API calls to Chat Engine.
Open the functions/src/index.js file in your IDE and the following code:
import * as functions from "firebase-functions";
exports.createChatEngineUser = functions.auth.user().onCreate((user) => {
console.log("create", user);
});
exports.deleteChatEngineUser = functions.auth.user().onDelete((user) => {
console.log("delete", user);
});This will simple state when a user signs up or deleted their account.
To deploy this, just run firebase deploy. You will be prompted to enable Authentication and the Blaze plan if you haven't already.
I just enabled Google Authentication and followed the steps in the website.
firebase deployYour backend be good to go! And you should see the following in your functions tab online:
[TODO: image of firebase console]
Now, let's connect this backend to ChatEngine.io!
Chat Engine's APIs let us host chatrooms on their platform, and provide us tools to build pretty chat UIs. We'll be signing up our new Firebase users to Chat Engine automatically here.
Here is the documentation on Chat Engine's APIs: https://rest.chatengine.io
In particular, we'll be using their Create User API and their Delete User API.
To start, go to https://chatengine.io, create an account, and set up a new Project. The Private Key and Project ID will be needed for our API calls.
Now, let's add the code to functions/src/index.js and be sure to replace XXX and YYY with your Private Key and Project ID, respectively.
import * as functions from "firebase-functions";
const axios = require("axios");
exports.createChatEngineUser = functions.auth.user().onCreate((user) => {
axios.post(
"https://api.chatengine.io/users/",
{
username: user.email,
secret: user.uid,
email: user.email,
first_name: user.displayName,
},
{ headers: { "Private-Key": "8c63dbce-80a7-455a-890b-9368d16e1dcb" } }
);
});
exports.deleteChatEngineUser = functions.auth.user().onDelete((user) => {
axios.delete("https://api.chatengine.io/users/me/", {
headers: {
"Project-ID": "794653df-052a-4ad2-b0aa-d6c251a10aef",
"User-Name": user.email,
"User-Secret": user.uid,
},
});
});If you're using TypeScript, you may need to modify the rules in .eslintrc.js like I did below:
rules: {
// prettier-ignore
"quotes": ["error", "double"],
"import/no-unresolved": 0,
// prettier-ignore
"indent": ["off", "always"],
"object-curly-spacing": ["off", "always"],
"@typescript-eslint/no-var-requires": ["off", "always"],
}Our backend is complete! Now users have access to Chat Engine when they sign up and their account goes away is they delete their data!
Let's setup a frontend and connect it to Firebase and Chat Engine!
At the top level of our project, we'll run the command below to make a NextJS project. Be sure to select the following too:
npx create-next-app@latest --ts- Name the project "frontend"
- "No" for using .eslint
- "No" for using the
src/directory - "No" for using the
app/directory - "Yes" for using the
@imports
I highly recommend TypeScript with Chat Engine, you'll see why soon :)
Now you should have a frontend/ folder right next to functions/.
Important side note: Replace styles/gloabl.css with the following code too.
Let's CD into this folder, install dependencies, and run in dev-mode.
cd frontend
npm install
npm run devAnd now you should see a pretty website on http://localhost:3000
For the final push, let's add all the code we need to our frontend!
To start, diable StrictMode in next.config.js and re-run the server with yarn dev.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
};
module.exports = nextConfig;This allows us to connect web-sockets in a NextJS project.
Next, let's delete the following:
styles/Home.module.css- The
pages/apidirectory
Next, let's create an auth page (pages/AuthPage.tsx) with the following code
export default function Page() {
return <div />;
}Next, let's create an chats page (pages/ChatsPage.tsx) with the following code
pages/ChatsPage.tsx
import { User } from "firebase/auth";
interface ChatProps {
user: User;
}
export default function Page(props: ChatProps) {
return <div />;
}Next, add a loading page (pages/Loading.tsx) with the following code:
export default function Loading() {
return (
<div className="page">
<div className="logo">☝️</div>
<div className="text">Loading your info...</div>
</div>
);
}Finally, modify the pages/index.tsx file to load the pages based on auth state:
import { useState } from "react";
import AuthPage from "./AuthPage";
import ChatPage from "./ChatsPage";
import Loading from "./Loading";
import { auth } from "@/firebase";
import { User } from "firebase/auth";
export default function Home() {
const [user, setUser] = useState<User | null>();
auth.onAuthStateChanged((user) => setUser(user));
if (user === undefined) {
return <Loading />;
} else if (user === null) {
return <AuthPage />;
} else {
return <ChatPage user={user} />;
}
}- npm install firebase
- create a .env file and replace with your values
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyDqXgd1AwRdqSCpdrhf_t0-rB1MuE2sd4A
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=chat-rce.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=chat-rce
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=chat-rce.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=45443650042
NEXT_PUBLIC_FIREBASE_APP_ID=1:45443650042:web:2598e30783c0e0bd545443
NEXT_PUBLIC_FUNCTIONS_URL=https://us-central1-chat-rce.cloudfunctions.net/v1
NEXT_PUBLIC_CHAT_ENGINE_PROJECT_ID=16b08e85-e4c9-4541-b30e-45e157ec3821
Make sure NEXT_PUBLIC_CHAT_ENGINE_PROJECT_ID is there with yuor project ID.
- add this code to firebase.ts
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
export const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
Now add this code to AuthPage:
import { auth } from "@/firebase";
import { signInWithRedirect, GoogleAuthProvider } from "firebase/auth";
export default function AuthPage() {
const onClick = () => {
signInWithRedirect(auth, new GoogleAuthProvider());
};
return (
<div className="page">
<div className="logo">👋 💬 🤖 </div>
<div className="text">Welcome to ChatRCE</div>
<div className="text" style={{ paddingBottom: "16px" }}>
Log in with your account to continue
</div>
<button className="button" onClick={onClick}>
Log In
</button>{" "}
<button className="button" onClick={onClick}>
Sign Up
</button>
</div>
);
}
Finally, let's connect our new user to Chat Engine using react-chat-engine-pretty!
Modify pages/ChatsPage.tsx with the following code:
import { auth } from "@/firebase";
import { signOut, User } from "firebase/auth";
import { PrettyChatWindow } from "react-chat-engine-pretty";
interface ChatProps {
user: User;
}
export default function Page(props: ChatProps) {
return (
<div style={{ height: "100vh" }}>
<button
style={{ position: "absolute", top: "0px", left: "0px" }}
onClick={() => signOut(auth)}
>
Sign Out
</button>
<PrettyChatWindow
projectId={process.env.NEXT_PUBLIC_CHAT_ENGINE_PROJECT_ID || ""}
username={props.user.email || ""}
secret={props.user.uid}
style={{ height: "100%" }}
/>
</div>
);
}And we're done!