In this tutorial, we will build a simple blog application with a built-in Google authentication system. Our blog will have two sections: a public section where readers can view published blog posts and a private section where authors can create and publish new blog posts. Gadget will handle all parts of our application stack, including the database, backend API, and React frontend.
Let's get started!
Head over to gadget.new to create a new Gadget app! You will have 3 choices for app templates - for this tutorial, we can use the Web app template that comes with built-in authentication.
Once the new Gadget app is created, move on to Step 2.
The first thing we can tackle is creating a model to represent our blog posts and store blog post data in our database.
Our blog's schema is going to need a post model where each record represents a blog post. To create the post model click on the + icon to the right of Data Models and name the model post.
Every model in Gadget comes with a set of non-configurable system fields:
Let's teach Gadget that beyond these system fields, our blog posts have additional attributes. We can do this by adding fields.
Now, each blog post will have a title value stored in this field.
Finally, let's add a couple of validations to this field.
Once finished, our title field should look like this:
Let's add some more fields to our post model to store the blog content, and published state:
Finally, let's keep track of what user wrote each blog post by adding a relationship field to post. Relationships allow us to model data in a normalized fashion, so we can create relationships between models that are otherwise unrelated to each other. Like foreign keys in SQL, one model receives and stores the id in its data to set up the relationship.
When we selected the Web app blog post template, Gadget created a user model for us to store information about our users. We can use this model to store information about the authors of our blog posts. Let's create a relationship between the post and user models.
One user can write many blog posts, so we need a one-to-many relationship where user has many posts:
The relationship should look like this:
You are done with data modeling! Now we move on to set our backend API permissions.
As we've been building our model and adding fields, Gadget has been automatically generating a set of actions for us. Actions are the API endpoints that we can use to create, read, update, and delete records in our database. We will use this generated CRUD API to build our blog. These actions can be found in the ACTIONS panel above the FIELDS when viewing a model.
But before we start building our frontend, we need to make sure we have the correct permissions set for different types of users. We want to make sure that only signed-in users can create and publish blog posts, and that anyone can read published blog posts.
By default, users who use Gadget's built-in authentication will be assigned the signed-in role. This is where we can configure what actions users with this role can perform. We also have an unauthenticated role that we can configure for users who are not signed in. These roles do not have permissions to custom model actions by default, so we will need to add permissions to be able to create, update, and read blog posts.
Start by granting unauthenticated users read permission on the post model. This will allow anyone to read published blog posts:
Now anyone will be able to read published posts!
Let's update the signed-in role to allow users to create and update blog posts:
Now signed-in users will be able to read, create, and update blog posts! This tutorial does not cover the deletion of posts, but you could also set that permission if you wish.
Finally, let's make sure that unauthenticated users can only read published blog posts. We can do this by adding a custom Gelly filter to the post model:
This Gelly snippet filters results returned from any read action called on the post model by unauthenticated users against the isPublished field on post. This means that only published blog posts will be returned to unauthenticated users.
What is Gelly? Can I eat it?
Gelly is Gadget's data access language, a superset of GraphQL that allows us to make queries (or in this case, filters!) against the database. For more info on Gelly, check out our docs!
Our API permissions are set, now we can build our frontend!
Now that we have our data model and API permissions set up, we can build our frontend. Gadget is built on Node.js, so we can install npm packages like we would in any other Node project. Let's start by installing our dependencies.
Our frontend will make use of the Chakra UI component library to handle layout and styling, chakra-ui-markdown-renderer + react-markdown + react-rte to help us display and edit blog post content. To install these dependencies, we will use the Gadget command palette:
The packages will be installed for you. You can view and edit these dependencies inside your project's package.json file like you would with any other Node project.
Now that we have our dependencies installed, let's start building our frontend!
Before we get into building out our routes, pages, and components, we should set up our ChakraProvider. This will allow us to use Chakra UI components in our app.
Finally, we can start building our frontend! The first thing to put together is our frontend routing and we will use react-router-dom to manage this. We will have two routes in our app: the root route / where anyone can read blog posts, and a route for logged-in users to write and publish blog posts at /admin.
We will make use of Gadget's @gadgetinc/react package to help make requests to our backend and handle authentication. This package has a suite of useful hooks and components we can use to protect routes behind authentication and make requests to our backend.
Gadget currently supports authentication via Google's OAuth 2.0 client. When you start building, you can use Gadget-supplied credentials so you don't need to worry about setting up an OAuth Client in the Google Cloud Console. The default frontend app has a sign-in button and a UserCard component which will allow you to test out the default authentication flow and gives you a taste of what using the useUser hook looks like. We will use these default credentials to build and test, but you will need to set up your own OAuth Client in the Google Cloud Console to use auth in production.
We can open frontend/App.jsx to set up our required routes:
The SignedInOrRedirect component can be used to protect routes, and will redirect unauthenticated users to the route defined in signInPath.
Right now, our routes are set up to render some placeholder DOM elements. You can preview your app by:
We just see a page with a header, the default app background, and the word "blog" displayed. This probably isn't the blog content we want to display, so let's go ahead and replace this with a proper navigation component so we can route to our blog posts and admin pages.
The default Gadget template gives us a Header component that we will modify. The Header is already included in our app, and the component is found in frontend/App.jsx.
Right now, the Header displays a sign-in or sign-out button, as well as our app name. We want to include some clickable links for our blog and admin routes, so let's update the Header component to include these links. We also want to use Chakra UI to change the style of the Header. The whole code snippet will be posted first, with details following.
There are a couple of important things to pay attention to in the Header component, most notably the hooks and components imported from @gadgetinc/react:
We can test out sign-in and sign-out now. When we sign in, we automatically redirect to the admin page and we can see an Admin link in the nav bar. Clicking on the Blog link in our header should change the placeholder text from "admin" to "blog", which means our router is working. Sign out, and you should be redirected back to the "blog" page.
Now that our router is working we can focus on building pages for reading and writing blog posts. Let's start with a page for reading blog posts.
Start by building a page that will display posts to unauthenticated users. This page will be the root route / and will display a list of blog posts.
Most of this file is ordinary React state and component code! There are some Gadget-specific things to pay attention to:
One more step until we can see our blog posts - let's hook up this page to our router in frontend/App.jsx:
Now if we check our development app, we should see... a message that tells us we haven't created any posts yet.
That's a bit anti-climatic. Let's fix that next.
This is the last major step in building our blog. We'll build a page that authenticated users can use to write blog posts. This page will be at the /admin route.
This file is a bit longer than the other files we've written so far, but it's still mostly ordinary React code. There are a few Gadget-specific things to pay attention to:
Once again, let's hook up this page to our router in frontend/App.jsx:
And we're done building our app! Let's check it out in the browser and write a blog post! Once you have a post written, you can publish it by toggling the switch in the "Publish" tab.
Try logging in and out, and different publishing states to test out the experience for both signed-in and unauthenticated users. See the video at the start of the tutorial for a demo.
If you want to publish your blog app to Production, you'll need to supply your own Google OAuth credentials. Read our documentation on how to set up credentials.
Finally, deploy your app to Production:
Your app will be bundled and minimized for production, and deployed. Preview your production app by:
Interested in building apps using OpenAI's API? Check out our OpenAI tutorial to learn how to use Gadget's some of Gadget AI tooling, including the OpenAI connection, vector embeddings, and response streaming.
If you have any questions, feel free to reach out to us on Discord to ask Gadget employees or the Gadget developer community!