Real-world app example - Real-time Editor, using FaunaDB (realtime stream), Reaflow (graph editor), Next.js framework and a bit of Magic (auth)!
This project is a Real-World App featuring FaunaDB as real-time database, Reaflow as graph editor, and Magic Link for passwordless authentication.
It also uses the famous Next.js framework, and it's hosted on Vercel.
This RWA is meant to help beginners with any of the above-listed tools learn how to build a real app, using best-practices. Therefore, the codebase is heavily documented, not only the README but also every file in the project.
The app allows users to create a discussion workflow in a visual way. It displays information, questions and branching logic (if/else). It works in real-time for better collaboration, and provides features similar as if you'd be building a Chatbot discussion.
Take a look at the Variants below before jumping in the source code. As part of my developer journey, I've reached different milestones and made different branches/PR for each of them. If you're only interested in Reaflow, or Magic Auth, or FaunaDB Real-Time streaming, they'll help you focus on what's of the most interest to you.
If you like what you're seeing, take a look at Next Right Now, a production-grade boilerplate for the Next.js framework.
Demo (automatically updated from the master
branch).
This RWA comes with the following features:
start
, end
, if
, information
, question
) with different layouts for each type (see NodeRouter component)
foreignObject
, which complicates things quite a bit (events, css), but it's the only way of writing HTML/CSS within an SVG rect
(custom nodes UI)foreignObject
and best-practicesBlockPickerMenu
componenttextarea
Recoil
(ctrl/cmd)+h
)token
cookie that can only be read/written from the server side (httpOnly
)/api/login
endpoint that reads the token on the server side and returns its content, used by the frontend to know if the current user is authenticatedCanvasDataset
) are persisted in FaunaDB and automatically loaded upon page loadtoken
cookie. This token is linked to the user and hold the permissions granted to the user.
Therefore, it will only allow what's configured in the FaunaDB "Editor" role.fauna-gql-upload
to sync the project's configuration with the FaunaDB database.yarn fauna:sync
..graphqlconfig
file to easily sync the FaunaDB GraphQL schema with the local project. (updates schema.graphql
)schema.graphql
is used by GraphQL queries/mutations and provides autocompletion and advances in-editor debugging capabilities.Known limitations:
RIGHT
(hardcoded) and adding nodes will add them to the right side, always (even if you change the direction)
While working on this project, I've reached several milestones with a different set of features, available as "Examples":
with-local-storage
(Demo | Diff):
with-faunadb-real-time
(with-faunadb-auth
, by using a stream manager.with-magic-link-auth
(with-faunadb-auth
(Demo | Diff):
with-fauna-fgu
(Demo | Diff):
yarn fauna:sync
automatically syncs the GraphQL schema, alongside indexes, roles, UDF, etc. It uses FaunaDB GraphQL Upload (FGU).with-fauna-graphql
(Demo | Diff):
Notes:
- The last example is always available in the
main
branch.- Although there are multiple examples to ease understanding of what's changed for each step, I strongly recommend using the latest version of the source code if you wish to implement your own version. This is because the latest version fixes a lot of downstream issues and benefits from the latest patches and updates. I haven't fixed issues in old examples, and I won't.
If you want to use this project to start your own, you can either clone it using git and run the below commands, or "Deploy your own" using the Vercel button, which will create for you the Vercel and GitHub project (but won't configure environment variables for you!).
yarn
yarn start
cp .env.local.example .env.local
, and define the FGU_SECRET
environment variableyarn fauna:sync
will create all collections, indexes, roles, UDF in the Fauna database related to the FGU_SECRET
environment variableNEXT_PUBLIC_SHARED_FAUNABD_TOKEN
and FAUNADB_SERVER_SECRET_KEY
can only be created once roles have been created during the previous step when running yarn fauna:sync
)If you deploy it to Vercel, you'll need to create Vercel environment variables for your project. (see .env.local.example
file)
Note: The current setup uses only one environment, the dev/staging/prod deployments all use the same database.
Deploy the example using Vercel:
Here are the future variants I intend to work on:
fauna-gql-upload
, the project is much easier to manage now, but it's still lacking observability.
I want to bring high observability to quickly understand from where errors come from, by having a whole test suite built around the project, and testing every role and FQL/GQL functions.
This might use fauna-schema-migrate
once it's more mature. (it'd replace the current fauna-gql-upload
)External help on those features is much welcome! Please contribute ;)
This section is for developers who want to understand even deeper how things work.
Users can be either Guests or Editors.
All requests to FaunaDB are made from the frontend. Even though, they're completely secure due to a proper combination of tokens and roles/permissions.
By default, users are guests. Guests all share the same working document and see changes made by others in real-time. They can only access (read/write) that special shared document.
Guests use a special FaunaDB token generated from the "Public" role. They all share that same token. The token doesn't expire. Also, the token only allows
read/write on the special shared document (ID: "1"), see the /fql/setup.js
file "Public" role.
Therefore, the public token, even though it's public, cannot be used to perform any other operation than read/write that single document.
Editors are authenticated users who can only access (read/write) their own documents.
A editor-related token is generated upon successful login and is used in the client to authenticate to FaunaDB. Even though the token is used by the browser,
it's still safe because the token is only readable/writeable from the server. (httpOnly: true
)
Also, the token won't allow read/write on other documents than their owner, see the /fql/setup.js
file "Editor" role.
Users authenticate through Magic Link (passwordless) sent to the email they used. Magic helps to simplify the authentication workflow by ensuring the users use a valid email (they must click on a link sent to their email inbox to log in).
When the user clicks on the link in their inbox, Magic generates a DID token
, which is then used as authentication Bearer token
and sent to our /api/login
.
The /api/login
endpoint checks the DID token and then generates a FaunaDB token (faunaDBToken
) attached to the user. This faunaDBToken
is then stored in
the token
cookie (httpOnly), alongside other user-related information (UserSession object), such as their email
and FaunaDB ref
and id
.
This token will then be read (/api/user
endpoint) when the user loads the page.
Even though there are 2 buttons (login/create account), both buttons actually do the same thing, and both can be used to sign-in and sign-up. That's because we automatically log in new users, so whether they were an existing user or not doesn't change the authentication workflow. It made more sense (UX) to have two different buttons, that's what people usually expect, so we made it that way.
The editor provides a GUI allowing users to add "nodes" and "edges" connecting those nodes. It is meant to help them build a workflow using nodes such as " Information", "Question" and "If/Else".
The workflow in itself doesn't do anything, it's purely visual. It typically represents a discussion a user would have with a Chatbot.
The whole app only use one page, that uses Next.js SSG mode (it's statically rendered, and the page is generated at build time, when deploying the app).
Once the user session has been fetched (through /api/user
), the CanvasContainer
is rendered. One of its child component, CanvasStream
automatically opens
a stream connection to FaunaDB on the user's document (the shared document if Guest, or the first document that belongs to the Editor).
When the stream is opened, it automatically retrieves the current state of the document and updates the local state (Recoil).
When changes are made on the document, FaunaDB send a push notification to all users subscribed to that document. This also happens when the user X updates the document (they receives a push notification if they're the author of the changes, too). In such case, the update is being ignored for performances reasons (we don't need to update a local state that is already up-to-date).
I strongly suggest reading:
I used FaunaDB GraphQL Editor to generate our schema visually, because I'm not so familiar with GraphQL schema definition (the server-side part of GQL).
Disclaimer: I'm the author of FaunaDB GraphQL Editor
The fauna/gql/source-schema.gql
file contains only the GraphQL types, it is the input schema that'll be used to generate the schema.graphql
file.
The fauna/gql/source-schema.gql
file is uploaded to FaunaDB GraphQL endpoint by FaunaDB GQL Upload when running yarn fauna:sync
.
There, FaunaDB has some internal magic that will create a new schema that you can see in the FaunaDB Dashboard > GraphQL.
Note: The
fauna/gql
folder is being ignored by WebStorm to avoid conflicting with theschema.graphql
which is the one we really want to use for autocompletion.
I use the GraphQL Config WebStorm plugin which send an introspection request to https://graphql.fauna.com/graphql
using an Admin/Server key to authenticate.
This generates the schema.graphql
, which is then made available to all our GraphQL files and provides auto-completion and advanced helpers when writing GraphQL queries/mutation.
ELKjs (and ELK) are used to draw the graph (nodes, edges). It's what Reaflow uses in the background. ELK stands for Eclipse Layout Kernel.
It seems to be one of the best Layout manager out there.
Unfortunately, it is quite complicated and lacks a comprehensive documentation.
You'll need to dig into the ELK documentation and issues if you're trying to change how the graph's layout behaves. Here are some good places to start and useful links I've compiled for my own sake.
elkt <=> json
both ways
elkt
to a graph
layered
algorithm
Known limitations:
Here is a list of online resources and open-source repositories that have been the most helpful:
Understanding FaunaDB:
Authentication and authorization:
Real-time streaming:
FaunaDB Real-world apps (RWA):
FaunaDB FQL:
FaunaDB GQL:
FaunaDB DevOps:
FaunaDB Community resources:
The way the current real-time feature is implemented is not too bad, but not great either.
It works by syncing the whole dataset whether the remote document
(on FaunaDB) is updated, which in turn updates all subscribed clients (except the author).
While this works, changes from one client can be overwritten by another client when they happen at the same time.
document
means "Canvas Dataset" here. It contains allnodes
andedges
(and other props, likeowner
, etc.)
A better implementation would be not to stream the actual document
, but only the document's patches.
The whole document
would only be useful for the initialization of the app.
Then, any change should be streamed to another document which would only contain the changes applied to the initial document.
When such changes are streamed (patches), they should then be applied to the current working document, one by one, in order.
Each change/patch would represent a diff between the previous and after states of the document, they would only contain what have changed:
This way, when something changes, the client would resolve what's changed and stream the patch to the DB, which in turn would update all subscribed clients which would apply that patch.
Conflict may still arise, but they'll be limited to parts of the document that have been updated simultaneously (the same node, the same edge, etc.).
This would provide a much better user experience, because overwrites will happen much less often, and it'd increase collaboration.