Managing User Comments and Gated Content with Astro DB and Storyblok
Storyblok is the first headless CMS that works for developers & marketers alike.
Introduction
In a real-world application, content alone isn't sufficient. You need to manage users, enable interactions, and sometimes restrict access to exclusive content. Traditional CMS platforms like WordPress offer built-in solutions for these needs but lack the benefits of the headless approach.
Storyblok is a Headless CMS that offers a modern content editing experience. It includes many features that help content creators work efficiently but lacks built-in options for managing end users or subscribers. This limitation is by design, but you might need these features for your projects.
Astro is a popular framework, and we have a comprehensive guide on creating a Storyblok website with Astro. In this tutorial, we will use Astro with Astro DB to manage users, comments, and gated content, requiring users to log in to view private articles.
Explore a live demo of the project here.
This tutorial is not for beginners. It assumes familiarity with both Astro and Storyblok. We won’t cover the basics, so if you're not comfortable with these tools, please review our Astro Ultimate tutorial series first.
Each important section has a corresponding GitHub branch to help you follow along.
Setting Up Your Project
In Storyblok, create an Article content type with the following fields:
- title: Text
- excerpt: Textarea
- private: Boolean
- body: Richtext
Create a folder called articles and add a few articles, making sure some are marked as private. For the code, use the starting-point branch from the GitHub repo. The project includes routes for a homepage displaying all posts and individual article pages. Signup and Signin routes are styled but non-functional placeholders.
Crafting the Database Schema
We will create three tables in Astro DB:
With these three tables, we can implement authentication and commenting features for our website. Let's explore how to create an Astro DB and design this schema.
Integrating Astro DB
Astro's guide includes a section on Astro DB, which is helpful if you're not familiar with it. You can add a local database to your project by running:
While it's not yet connected to the online Astro Studio, we can start adding tables to our local database. Later, we can use the Astro CLI to push this schema to Astro DB and get our project running with Astro Studio.
DB Schema Definition in Astro
After running the astro add db
CLI command, you'll have a db
folder in your project's root directory with a config.ts
and a seed.ts
. Replace these files as follows:
We have replaced the default schema with the three-table schema we introduced in our database schema design section. As for the seed.ts
, let's leave it empty for now.
Implementing Lucia for Authentication
Lucia is an open-source, low-level authentication library. Astro recommends using Lucia in their documentation. Lucia also has a guide on how to set it up with Astro. While Lucia's guide is based on SQLite and not directly on Astro DB, it offers a great overview of how everything works. We recommend taking a look at that guide.
Let's create a new file src/lib/auth.ts
and add the following code:
If you have read the Lucia authentication documentation, this code should look familiar. While the guide uses the BetterSQLite3Adapter
, Astro DB is built with Drizzle ORM, so we use the @lucia-auth/adapter-drizzle
instead. The rest of the code is primarily TypeScript helpers.
Next, to keep everything clean, let's create a few helper functions that we will use multiple times:
Most of the code will look familiar if you have read the Lucia Astro authentication guide.
You can check out db-auth-init branch to match the project progress.
Creating the Signup Functionality
Before we write the code for signup, let's quickly review our pages/signup.astro
file. This file contains a form with three input fields: name, username, and password. When you submit the form, it makes a POST request to the /api/signup
route. If you try it now, it will throw a 404
error since there is no /api/signup
route yet. Let's create it.
The above code is straightforward and includes comments to clarify its function. Here's the workflow:
- We get the form data: name, username, and password.
- We validate the input using the helper functions created earlier.
- We check the database for an existing user with the same username. If a user exists, we return an appropriate message.
- If the username is unique, we hash the password, save the user information in the database, and create a session.
Updating the Signup Form
Now, let's add a few lines of code to the pages/signup.astro
file to connect the form to this API endpoint. Add the following JavaScript below the HTML markup:
This script overrides the default form submit behaviour and makes a POST request via fetch
. If the response is successful, it redirects to the home page. If there's an error, it displays the error message in the UI.
Building the Signin Functionality
The signin page will be almost identical to the signup page, with fields for username and password, and it will make a POST request to api/signin. Let's create the signin API endpoint with the following code:
This code is similar to the signup endpoint, with some differences:
- Validate the username and password.
- Check if a user with the given username exists. If not, return an appropriate response.
- Validate the password. If incorrect, return an appropriate response.
- If everything is correct, create a session and return a successful response.
Next, update the pages/signin.astro
file. Let's add the same JavaScript we used on the signup page with one minor change: update the form ID as shown below.
Only this line will be different. To be fair, you could create a wrapper function in a utils file and reuse it, but I'll leave that part for you to implement.
You can check out the signin-signup branch to match the project progress.
Developing Middleware for Session Management
To handle session management effectively, we'll create a middleware that validates the session and stores session data in Astro's locals. This allows us to access user and session information across different pages and display varying UI based on authentication status.
In this middleware, we:
- Validate the request origin for non-GET requests.
- Retrieve the session ID from cookies.
- Validate the session and handle session renewal if necessary.
- Store session and user data in Astro's locals for use in pages.
Additional Configurations
Update TypeScript Definitions: Add the following code to env.d.ts
to ensure TypeScript recognizes the session and user types:
Update Astro Configuration: For security, enable checkOrigin
in astro.config.mjs
:
Verifying the Setup
After setting up the middleware and making these configurations, you can test the authentication by logging in or signing up. Navigate to your home page and add the following code to check the session details:
This will log the logged-in user details and session information to the console. With this, we've covered the most complex part of the setup. In the next part, we can focus on tweaking the UI to reflect authentication status.
You can check out middleware branch to match the project progress.
Updating the UI Based on User Authentication
Now that we have logged in the user, let's update our UI to reflect this. First, let's modify our Header.astro
file. In the template, we already have some code that renders based on a predefined variable called user
. This variable is set to null
, so currently, the login link appears in the nav. If user
is not null
, it shows a profile page link and a sign-out link. Next to our site title, we also show the user name. We'll make this dynamic by setting user
to Astro.locals.user
.
Now, you will see changes in the header when you are logged in.
Let's quickly add the logout
API route:
Next, add the following JavaScript to the Header.astro
component to handle the sign-out process:
To prevent users from accessing private articles if they're not logged in, and to prevent logged-in users from accessing the signin and signup routes, add the following code:
In the signin
and signup
pages:
In the articles page:
With these changes, if an article is private and there is no logged-in user, attempting to access it will redirect the user to the signin page.
Now, you can test your project by signing in and out to ensure that the site is working as expected and that you can see the private content when logged in.
You can check out enhance-ui branch to match the project progress.
Displaying and Adding Comments to Articles
To display all the comments for an article, you can easily fetch them from the database. More details on reading from the database can be found here.
For the UI, here's an example of how you might structure it. Feel free to apply your own styles to match your design preferences.
In the code above, you'll notice that above the comments list, there's a section that checks if the user is signed in. If so, it displays a form for adding a comment; otherwise, it shows a sign-in link with a message encouraging the user to sign in to leave a comment.
The CommentForm
component is simply a form with a textarea. You can easily replicate this with the following code:
Now that we have the comment form displayed, let's ensure we save the data when a user leaves a comment.
More information on inserting data can be found in the Astro documentation. You can test your site by creating a few accounts, leaving different comments, and observing how it all works.
You can check out the add-comment branch to match the project progress.
Building the Profile Page
Lastly, let's create the missing profile page. Although the profile link is visible in the header when logged in, there isn't an actual profile page yet. Let's quickly create one:
If you've followed along so far, the above code should be familiar. With this, all the features we aimed to implement are complete.
You can check out main branch to match the project progress.
Connecting to Astro Studio
Finally, let's connect our project to Astro Studio. This step is straightforward and will enable you to manage your database and project more efficiently. Follow the guide from the Astro documentation to easily connect your local database to Astro Studio. Once connected, you can use Astro Studio to manage your database schema, user data, and more. This integration will streamline your workflow and enhance the overall project management experience.
By following these steps, you'll have a fully functional system for managing user comments and gated content with Astro DB and Storyblok.
Conclusion
In this tutorial, we've covered the essentials of managing user comments and gated content using Astro DB and Storyblok. By combining these powerful tools, we've built a robust system for user authentication, comment management, and gated content. From designing the database schema to setting up authentication with Lucia and enhancing the UI based on authentication status, each step is crucial for a seamless user experience.
Feel free to explore further and customize the implementation to fit your specific needs. With the basics in place, you're well on your way to creating dynamic and interactive web applications with Astro and Storyblok.
Name | Link |
---|---|
Live Demo | https://storyblok-astro-gated-content.vercel.app/ |
Project source code | https://github.com/storyblok/storyblok-astro-gated-content-tutorial |
Astro Storyblok Ultimate Tutorial | https://www.storyblok.com/tc/astro |
Astro DB | https://docs.astro.build/en/guides/astro-db/ |
Lucia Auth | https://lucia-auth.com/getting-started/astro |