Build an e-commerce application
This guide shows how you can use Fauna and Next.js to build a simplified e-commerce application.
The application is a marketplace where vendors can create their own stores to add and sell products. Customers can use the marketplace to buy products from different vendors.
You can use the application as a starting point for your own Fauna-powered applications.
The source code for the application is available on GitHub. You can find the live demo here.
Learning objectives
-
Compose Fauna queries with Fauna Query Language (FQL)
-
Use Fauna in a full-stack application
Data models
This application has the following data models:
-
User: Represents a user of the application
-
Shop: Represents a store created by a user
-
Product: Represents a product added to a store
Fauna is document-relational; it combines the flexibility and familiarity of JSON documents with the relationships and querying power of a traditional relational database.
You have the flexibility to store unstructured data while also getting the structured querying power of traditional SQL databases. You can define data relationships that are one-to-one, one-to-many, and many-to-many in Fauna.
Data model relationships
A user can have many shops. This is a one-to-many relationship.
A shop can have many products. This is a one-to-many relationship.
A shop can have many orders, and a user can order from multiple shops at the same time. There is a many-to-many relationship between shops and users.
Another collection called OrderDetail
is used to handle this relationship.
Create a database
To start, create a database in Fauna.
-
Log in to the Fauna dashboard.
-
Click Create Database to create a database.
-
Enter a database Name and enter the other database properties.
-
Click Create.
Create collections
Next, use the web shell to create collections for your database.
-
Navigate to Fauna web shell.
-
To create new collections, write the following FQL code into the web shell and run it.
It creates six collections:
Order
,OrderDetails
,Product
,Shop
,User
, andUserProfile
. Note the newly created collections in the Explorer window.
Create your data relationships
In this section, you generate data and data relationships in Fauna using FQL. You create one-to-many, many-to-many, and one-to-one relationships in Fauna using FQL queries for the e-commerce application.
One-to-many
Create a new user from the Fauna web shell. Run the following code to create a new user:
{
id: "360355394276556881",
coll: User,
ts: Time("2024-04-01T13:32:06.830Z"),
name: "test",
email: "test@example.com"
}
The id
and ts
are auto generated and may differ per document.
Copy the generated id
value for the next steps.
To establish a one-to-many relationship between a user and a shop, create a new
shop and link it to the user. Use the id
value of the user you created in the
previous step.
Run the following code:
To query data in a one-to-many relationship, run the following:
To get a particular shop’s owner, run the following query:
Many-to-many
In the sample app, a user can buy from many shops and a shop
sells to many users. Shop
and User
collections have a many-to-many
relationship through the OrderDetails
collection. It works like a join table
in SQL. The following code snippet demonstrates how you define it in FQL:
First create couple new products to add to the shop.
Use the id
value of the products you created in the previous
step to add them to the shop.
To find all the shops a user has shopped from, you can run the following FQL query:
Fauna indexes
Fauna uses indexes to efficiently and quickly retrieve and manipulate data based on various criteria defined by you. Fauna indexes help organize and optimize data queries in a Fauna database.
Run the following code in shell to create a byEmail
index on the User
collection.
This index helps you query users by email more efficiently.
To verify run the following query.
You can convert one of the previous queries to use an index. To find all shops that belongs to a particular user you can create an index.
You can run the following query to get shops by owner reference.
Using indexes in Fauna is a good practice to optimize your queries. The
.all()
and .where()
functions go through all the documents in the collection
to find the result. This can be slow if the collection has a large number of
documents. Defining an index on collections and using it in your queries can
help you optimize your queries.
User-defined functions
User-defined functions (UDFs) in Fauna are functions that you create and store in your Fauna database.
UDFs allow you to encapsulate logic and business rules in a centralized location, making it easier to manage and reuse across your applications. You can turn the product search logic from the previous section into a UDF by running the following FQL code.
Then, you can call the function as follows.
User authentication and ABAC
Fauna provides ABAC (Attribute-Based Access Control) to secure your data. This section demonstrates how to implement basic user authentication and ABAC in Fauna.
Securing application with ABAC
Instead of using the server key in your application you use a public key with limited privileges. The public key assumes a role that has privileges to do only read operations in all the Collection. Every other operation is restricted for this key.
Following diagram gives you a visualization of this.
Using the shell create a new Role called UnAuthenticatedRole
.
Next create a new key for the this role.
Run the following FQL code to create the Role and the key.
{
"id": "358243630271430723",
"coll": "Key",
"ts": "2023-03-04T05:09:59.240Z",
"secret": "fnAE-LyvBWAAQ_rdkz_DbDwSpgHt1pE5rRtweu4y",
"role": "UnAuthenticatedRole"
}
The returned secret is your application’s public key
(it assumes all the permissions granted to the UnAuthenticatedRole
user role).
This is the key you use to register and log-in users in your application.
This key has read-only permission to all your collections.
It can not be used to create, update, or delete any document in any collection.
The unauthenticated users need a way to signup and login into your application.
You create two user defined functions Signup
and Login
for this.
User signup
Create a new UDF called Signup
to allow users to register in your application.
Creating the signup function:
Run the following FQL code to create the Signup
function.
The FQL function body is written like a standard JavaScript arrow function.
When you call the function from your application you
provide username
, email
and password
as parameters;
you are able to define any other parameters you may
need here as well. In the function body you have
the Credentials.create
built in Fauna function, which securely
encrypts and hides the password.
{
"name": "Signup",
"coll": "Function",
"ts": "2023-03-05T18:31:16.835Z",
"body": "(username, email, password) => {\n let user = User.create({ name: username, email: email })\n Credentials.create({ document: user, password: password })\n }",
"role": "server"
}
You can call the function in the shell to test it.
In your application you provide the key for the
UnAuthenticatedRole
role.
The UnAuthenticatedRole
role should have the Call
privilege
to the Signup
function.
Remember that the UnAuthenticatedRole
role should have
read-only access to all collections.
Apply these privileges to the UnAuthenticatedRole
role by running
the following Fauna code.
User login
Create a new UDF called Login
to allow users to log in to your application.
Similar to the Signup
function, the Login
function takes
email
and password
as parameters.
{
"name": "Login",
"coll": "Function",
"ts": "2023-03-05T19:02:35.535Z",
"body": "(email, password) => {\n let user = User.byEmail(email).first()\n Credentials.byDocument(user).login(password)\n }",
"role": "server"
}
If email and password match the data saved in the database then
Fauna issues a temporary key to the application. This key
assumes the AuthenticatedRole
role.
The following diagram demonstrates this flow:
Run the following FQL code to create the AuthenticatedRole
role.
Run the Login
function in the shell to test it.
{
"id": "358257703275987011", //this id is unique and will be different for you
"coll": "Token",
"ts": "2023-03-04T08:53:40.310Z",
"secret": "...",
"document": {
"id": "358250589763665987",
"coll": "User",
"ts": "2023-03-04T07:00:36.380Z",
"name": "JonLuc",
"email": "picard@tng.com"
}
}
The Login
function returns a temporary key that you can use to access the
database resources. This key has the permissions defined in the
AuthenticatedRole
.
The UnAuthenticatedRole
role should have the Call
privilege to the Login
and Signup
function and read privileges to User
and Shop
collections.
Run the following FQL code to apply the privileges to the
UnAuthenticatedRole
role.
Run the app
To run the app:
-
Clone the repository from GitHub.
-
Create a new .env file in the root of your application and add the following environment variables. Generate a new secret key for your unauthenticated role by running the following Fauna code in the shell:
{ id: "394568774441762893", coll: Key, ts: Time("2024-04-08T04:02:36.250Z"), secret: "fnAFeco1uBAATZPOuclEAJ11MMVT_EDxGXy1vyb4", role: "UnAuthenticatedRole" }
Copy the secret key and add it to your .env file. Also include the
NEXT_PUBLIC_FAUNA_ENDPOINT
environment variable.NEXT_PUBLIC_FAUNA_SECRET="<Secret for UnAuthenticated Role>" NEXT_PUBLIC_FAUNA_ENDPOINT="https://db.fauna.com/"
Note that the NEXT_PUBLIC_FAUNA_SECRET
is the key of your
UnAuthenticatedRole
.
-
Run the following commands in your terminal:
cd fauna-shop-app npm install npm run dev
When the application is running, you can access it at http://localhost:3000. Go ahead and create a new user, add a shop, and add products to the shop. When you create a new user in the application you can see the user in the Fauna dashboard.
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!