Building A Serverless Live Chat App with Next.js, Fauna, and WunderGraph for GraphQL Live Queries
Join our Open Source Community!
Join our Discord community to explore the exciting world of Backend for Frontend (BFF) architecture! We're a group of web development enthusiasts who are passionate about creating scalable and efficient APIs that power modern web and mobile apps.
A Step-by-Step Guide to creating a Scalable, Real-time Chat App using Serverless technologies…with a little help from NextAuth.js for GitHub sign-ins. Who needs WebSockets when you’ve got Live Queries? Not us!
If you’re building apps that work with realtime data, you’re probably using WebSockets. These allow a web browser and a web server to communicate in real-time by keeping a persistent connection open between the two – data is pushed to clients as soon as it becomes available, rather than having the client constantly poll the server to check for new data.
But what if your app is serverless – running on infrastructure managed by a cloud provider like AWS or GCP?
To facilitate high elasticity and fault tolerance, these environments are designed to be stateless and ephemeral by their very nature, meaning there’s no guarantee that your code will be running on the same physical server from one request to the next – and thus no persistent connection between the client and the server.
So what’s the solution to building realtime apps on serverless architectures? Let’s find out! Let’s build this realtime Slack/Discord-esque live chat using Next.js as our JS framework, Fauna (using GraphQL) as our database, and WunderGraph as a backend-for-frontend that facilitates the link between the two. Our app will also use GitHub sign-ins, and we’ll use the famous NextAuth (Now Auth.js!) for our auth needs.
Before we begin, though, let’s talk about how exactly we plan to solve the realtime data problem if we can’t use WebSockets.
Live Queries on the Server - The Secret Sauce
The GraphQL specification defines Subscriptions – you set up a stateful WebSocket connection between the client and server, and then subscribe to an event on the server. When the server sees an event that matches a defined Subscription, it sends requested data over the WebSocket connection to the client – et voila, we have our data.
💡 This explanation is a little handwave-y, but bear with me. Going over the differences between the transports graphql-ws (GraphQL over WebSocket), graphql-helix (GraphQL over SEE) and @n1ru4l/socket-io-graphql-server (GraphQL over Socket.io) are a little beyond the scope of a tutorial.
When you can’t use WebSockets (on serverless platforms, as mentioned before), you can’t use Subscriptions…but that’s where Live Queries come in.
They aren’t part of the GraphQL spec, so exact definitions differ by client library, but essentially, unlike Subscriptions, Live Queries aim to subscribe to the current state of server data – not events (new payloads whenever the query would yield different data)...and unlike Subscriptions, when WebSocket connections aren’t available, they can use plain old client-side HTTP polling at an interval, instead.
I can see readers picking up their pitchforks, right on cue.Client-side polling for real-time data?! That’s insanely expensive, and what about handling multiple clients querying the same data?
And you’d be right! But…that’s where WunderGraph comes in. You see, we’re not going to be polling this data client-side. Instead, we’re pushing these Live Querying responsibilities onto Wundergraph, our Backend-for-Frontend, or API Gateway, whatever you want to call it.
WunderGraph is a dev tool that lets you define your data dependencies – GraphQL, REST APIs, Postgres and MySQL databases, Apollo Federations, and anything else you might think of – as config-as-code, and then it introspects them, turning all of it into a virtual graph that you can then query and mutate via GraphQL, and then expose as JSON-over-RPC for your client.
When you’re making Live Queries from the WunderGraph layer, no matter how many users are subscribed to your live chat on the frontend, you’ll only ever have a single polling instance active at any given time, for your entire app.
Much better than raw client-side polling; but is this perfect? No – the polling interval still adds latency and isn’t ideal for apps that need truly instant feedback, and without a JSON patch, the entire result of a query will be sent over the wire to the client every single time, even the unchanged data – but for our use-case, this is just fine.
With all of that out of the way, let’s get to the coding!
First off, let’s talk about the flow of this app.
Our users sign in with their GitHub account (via OAuth) for the chatroom. We’re building a workgroup/internal team chat, and using GitHub as the provider for that makes sense.
Using NextAuth greatly streamlines our auth story – and it even has an official integration for Fauna – a serverless database! This makes the latter a really good pick as a database for both auth and business logic (storing chat messages).
Our Next.js frontend is relatively simple thanks to WunderGraph – it’ll provide us with auto-generated (and typesafe!) querying and mutation hooks built on top of Vercel’s SWR, and that’s what our components will use to fetch (chat messages, and the list of online users) and write (new chat messages) data.
Let’s get started!
Step 0: The Setup
NextJS + WunderGraph
create-wundergraph-app CLI is the best way to set up both our BFF server and the Next.js frontend in one go, so let’s do just that. Just make sure you have the latest Node.js LTS installed, first.
Then, cd into the project directory, and
Our NextAuth setup involves a little busywork. Let’s get the base package out of the way first.
Next, get the NextAuth adapter for FaunaDB. An “adapter” in NextAuth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc. Adapters are technically optional, but since we want to persist user sessions for our app, we’ll need one.
Finally, we’ll need a “provider” – trusted services that can be used to sign in a user. You could define your own OAuth Provider if you wanted to, but here, we can just use the built-in GitHub provider.
Read the GitHub OAuth docs for how to register your app, configure it, and get the URL and Secret Key from your GitHub account (NOT optional). For the callback URL in your GitHub settings, use
Finally, once you have the GitHub client ID and secret key, put them in your Next.js ENV file (as
Step 1: The Data
Fauna is a geographically distributed (a perfect fit for the serverless/Edge era of Vercel and Netlify) document-relational database that aims to offer the best of both SQL (schema-based modeling) and NoSQL (flexibility, speed) worlds.
It offers GraphQL out of the box, but the most interesting about Fauna is that it makes it trivially easy to define stored procedures and expose them as GraphQL queries via the schema. (They call these User Defined Functions or UDFs). Very cool stuff! We’ll make use of this liberally.
- Sign up at Fauna
- Create a database (without sample data),
- Note down your URL and Secret in your Next.js ENV (as FAUNADB_GRAPHQL_URL and FAUNADB_TOKEN respectively) and move on to the next step.
To use Fauna as our Auth database, we’ll need to define its Collections (think tables) and Indexes (all searching in Fauna is done using these) in a certain way. NextAuth walks us through this procedure here , so it’s just a matter of copy-pasting these commands into your Fauna Shell.
First, the collections...
...then the Indexes.
Now that we have Fauna set up to accommodate our auth needs, go back to your project directory, and create a
./pages/api/auth/[...nextauth.ts] file with these contents:
That’s it, we have some basic auth set up! We’ll test this out in just a minute. But first…
Once we’re done with Auth, it’s time to define the GraphQL schema needed for our business logic, i.e users, chats, and sessions (with slight modifications to account for the existing NextAuth schema). Go to the GraphQL tab in your Fauna dashboard, and import this schema.
chat have a proper relation to
users, but not
sessions? This is a limitation of the way NextAuth currently interacts with Fauna for storing user sessions - but we'll get around it, as you'll soon see.
See that last query marked with a @resolver directive? This is a Fauna stored procedure/User Defined Function we'll be creating! Go to the Functions tab and add it.
Being familiar with the FQL syntax helps, but these functions should be self explanatory – they do exactly what their names suggest – take in an argument, and look up value(s) that match it using the indexes defined earlier. When used with the
@resolver directive in our schema, they are now exposed as GraphQL queries – incredibly useful. You could do almost anything you want with Fauna UDFs, and return whatever data you want.
Step 2 : Data Dependencies and Operations
WunderGraph works by introspecting all data sources you define in a dependency array and building data models for them.
First, make sure your ENV file is configured properly…
Replace with your own values, obviously. Also, double check to make sure the Fauna URL is set to the region you’re hosting your instance in!
…and then, add our Fauna database as a dependency in WunderGraph, and let it do its thing.
You can then write GraphQL queries/mutations to define operations on this data (these go in the .wundergraph/operations directory), and WunderGraph will generate typesafe Next.js client hooks for accessing them.
Getting all messages here, along with their related Users.
The way NextAuth works with GitHub is, it stores currently active sessions in the database, with an
expires field. You can read the
userId from the active
sessions table, and whoever that
userId belongs to can be considered to be currently online.
So, this GraphQL query fetches all users who currently have an active session - we'll use this in our UI to indicate the ones who are online. WunderGraph makes it trivial to perform JOINs using multiple queries - and using its
_join field (and the
@transform directive to cut down on unnecessary nesting), we can overcome NextAuth not adding the proper relations in Fauna - seen here while fetching the user linked with a session by their userId.
With a brief enough time-to-live for GitHub OAuth tokens, you will not have to worry about stale data when it comes to online users, and NextAuth is smart enough to invalidate stale tokens anyway when these users try to login with an expired token.
And finally, this is our only mutation, triggered when the currently signed-in user sends a new message. Here you see how Fauna handles relations in Mutations - the
connect field (read more here ) is used to connect the current document being created(a chat message), with an existing document (a user)
Step 3 : The Root
You’ll have to wrap your app in <SessionProvider> to be able to use NextAuth’s useSession hooks, by exposing the session context at the top level of your app.
Also I’m using TailwindCSS for styling – instructions for getting it set up with Next.js here.
NextAuth’s hooks make it trivially easy to implement authorization – the second part of auth. Use useSession to check if someone is signed in (this returns a user object with GitHub username, email, and avatar URL – nifty!), and the signIn and signOut hooks to redirect users to those pages automatically.
You can style custom NextAuth signIn/signOut pages yourself if you want (instructions here ) but the default, unbranded styles work just fine for our needs.
Step 4 : Online Users
Nothing much to see here; our strategy for determining online users was mentioned in the GraphQL query for this already.
Step 5 : The Chat Window (Feed and Input)
Here, we see how WunderGraph can turn any standard query into a Live Query with just one added option – liveQuery: true. To fine-tune polling intervals for Live Queries, though, check out
./wundergraph/wundergraph.operations.ts, and adjust this value in seconds.
For the timestamp, we’ll use a simple utility function to get the current time in epoch – the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT) – and converting it into human readable string to be stored in our database.
Step 6 - The Navbar
The NavBar component uses NextAuth’s useSession hooks again – first to get the current user’s GitHub name and avatar URL and render them, and signOut to…well, sign out.
That’s All, Folks!
Bringing together NextAuth, Fauna, and WunderGraph for GraphQL Live Queries is a powerful combination that will serve you well in creating real-time, interactive chat experiences - whether you're building something simple for a small community, or complex, enterprise-level platforms.
For Serverless applications, using WunderGraph as a backend-for-frontend with GraphQL Live Queries will give you far better latency than client-side polling - only one polling instance for all clients subscribed to the chat app, equals reduced server load and network traffic.
Here are some things you should note, though, going forward:
For Quality of Life
liveQuery: truecommented out while you’re building the UI, and only turn it on while testing the chat features. You don’t want to thrash the Fauna server with calls – especially if on the limited free-tier!
If you make changes to your Fauna GraphQL Schema during development, you’ll probably find that WunderGraph’s introspection doesn’t catch the new changes. This is intentional – it builds a cache after first introspection, and works off of that instead because you don’t want WunderGraph (which, in production, you’d deploy on Fly.io or WunderGraph Cloud) to waste resources doing full introspections every single time.
To get around this, run
npx wunderctl generate –-clear-cacheif you’ve made changes to your database schema during development and want the models and hooks regenerated.
Deploying this combination is pretty easy; WunderGraph is a Go server and can be deployed to Fly.io or WunderGraph’s managed cloud platform, WunderGraph Cloud , Fauna has your data in managed instances anyway, and your Next.js frontend can be deployed to Vercel or Netlify.
Speaking of which — set the
NEXTAUTH_URLenvironment variable to the canonical URL of the website when you deploy.
If you need something clarified re: WunderGraph as a BFF/API Gateway, head on over to their Discord community, here . Happy coding!