Strapi is a headless CMS built entirely with JavaScript. Whether your preference leans more toward REST or GraphQL, Strapi offers built-in support for both. As a result, you can use the frontend of your choice and simply connect to the Strapi API.
Strapi also offers some powerful database engines for you to create complex queries. In November of 2021, Strapi announced the launch of Strapi v4, which comes with the Query Engine API. With this new feature, developers are now able to create custom database queries, like joining two collections to filter and fetch data. This is particularly helpful for developers who have complex needs. For example, in the context of a blog, you could query all the categories used in 2022 and fetch only the relevant articles.
In this article, you’ll be building a personal habit tracker. To begin, you will set up your Strapi backend project and build a few collections for your habits and logs to track completion. Then you’ll set up your React project to create, display, and check off habits. Finally, you’ll discover how to create custom queries to fetch habits so it’s easier to track completion on the frontend.
At the end of this tutorial, you’ll have a habit tracker that will look like this:
As previously mentioned, Strapi v4 provides a Query Engine API that allows you to interact directly with the database by writing custom queries. Strapi’s REST API is extensive and comes with a lot of filtering options that will suit a wide range of developers. This includes options to filter whether a field is equal, greater, or smaller than a specified value. This flexibility lets developers query specific data without writing custom code. However, for projects with complex needs, it’s easier, and sometimes necessary, to give developers more direct access to their database.
For example, in your habit tracker project, you could do two API calls: one for the habits and another for the logs to check if a habit was completed that day. Or you could create a custom API endpoint that would return your list of habits. Each of these would include a completed
flag that would be set to true or false depending on your logs. This level of customization is one of the major advantages of the Query Engine API.
Another advantage is bulk operations. Whether it’s inserting, updating, deleting, or aggregating, developers can easily and quickly write database operations with the Query Engine API.
In this tutorial, you will build a habit tracker application. This project includes a Strapi backend to store your data and a React client to interact with it.
In a new terminal window, start by creating your Strapi backend:
npx create-strapi-app@latest my-project --quickstart
Once completed, your Strapi server will launch automatically and prompt you to register your first administrator:
After you’re done filling out your information, select Let’s start, and you’re ready to start creating some types.
Note: If you’re unfamiliar with the concept of types in Strapi, don’t hesitate to check out Strapi’s documentation to discover the different kinds of types.
Under Plugins, click on Content-Type Builder. Then under Collection Types, click on Create new collection type. This will open a window where you’ll be able to create your first type. In the input labeled Display name, enter “Habit”:
After clicking on Continue, you’ll be able to create the first field of your collection. For your habits, you’ll need two kinds of data. The first one is its name. The second is its type (to know whether the habit is part of the morning, afternoon, or evening routine).
To accomplish this, select the Text option. In the input labeled Name, put “name” and keep the Short text option selected. Then click on Add another field:
For the type, you need a field of type Enumeration. In the input labeled Name, put “type”, and for the values, add the following:
1 morning
2 afternoon
3 evening
This will guarantee that your type can only be one of those three approved values. Here’s what this step looks like:
Finally, select Finish.
You should now see your content type with your two fields. Click Save to persist your changes:
Next, continue by creating a new collection type called Habit Log. As the name suggests, the habit log type will keep a list of which habits were completed and when. As a result, your habit log type will have two fields:
To create this type, click on Create a collection type again, but this time, input “Habit Log” in the Display name input. Then click on Continue.
For the first field, you’ll use Relation to create a relationship with the Habit
collection. In the Field name, put “habit” and select the first relationship type. By choosing this type, you specify that the habit log has only one habit, but multiple logs can have the same habit (ie with different dates). Click on Add another field:
For the second field, choose the Date field type and enter “completionDate” as its name. For the Type, select date (ex: 01/01/2022):
Click on Finish and then Save to persist your new collection type.
Now that your Strapi backend is set up, it’s time to get your frontend up and running.
When you’re done setting up the frontend, it will look like this:
When completed, your Habit Picker application will have the following functionalities:
CreateHabitForm
that lets you or your users create new habits.Calendar
to select the day as well as view your habits and progress for that day.Habits
component that regroups your three sections (morning, afternoon, and evening).HabitsList
. This component will contain dummy data until you connect it to the backend.For this tutorial, you’ll use the MUI library (previously called Material-UI) to get access to a built-in datepicker. Thankfully, MUI comes with a starter project with Create React App.
To get started, in a new terminal window, run the following commands:
curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/create-react-app
cd create-react-app
npm install
Then install a few necessary libraries. In this project, you’ll be using one of the four date-libraries supported called date-fns. You also need axios
for making HTTP requests and qs
for parsing/creating queries. You can get all the libraries needed by running one command:
npm install @mui/x-date-pickers @date-io/date-fns date-fns axios qs
Inside your project and your src
folder, create a new file called Calendar.js
.
Because the date selected by the calendar will be shared not only with the calendar but also with the list of habits, you need to set this variable at the App.js
level and treat it as if you will receive calendarDate
and setCalendarDate
as props from the parent.
You should also set the maxDate
to today, as this prevents users from selecting future dates and checking off habits in advance:
1 import * as React from 'react';
2
3 import Grid from '@mui/material/Grid';
4 import TextField from '@mui/material/TextField';
5
6 import { StaticDatePicker } from '@mui/x-date-pickers/StaticDatePicker';
7 import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
8 import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
9
10 export default function Calendar({ calendarDate, setCalendarDate }) {
11 return (
12 <Grid item xs={6}>
13 <LocalizationProvider dateAdapter={AdapterDateFns}>
14 <StaticDatePicker
15 displayStaticWrapperAs="desktop"
16 openTo="day"
17 value={calendarDate}
18 maxDate={new Date()}
19 onChange={(newValue) => {
20 setCalendarDate(newValue);
21 }}
22 renderInput={(params) => <TextField {...params} />}
23 />
24 </LocalizationProvider>
25 </Grid>
26 )
27 }
In the same src
folder, create a new file called CreateHabitForm.js
. This component is a very straightforward form, and you need to create three variables: name
, type
, and alert
.
MUI comes with all the pre-built components you would need, including the following:
TextField
for inputsInputLabel
for labelsSelect
and Menu Item
to display a drop-downFormControl
for styling your formButton
to render a buttonAlert
to display a banner for messages (whether successful or not)With the mentioned components, you can quickly put a form together and link your name
and type
variables along with their setter functions. Here is the result:
1 import * as React from 'react';
2
3 import Grid from '@mui/material/Grid';
4 import Alert from '@mui/material/Alert';
5
6 import FormControl from '@mui/material/FormControl';
7 import TextField from '@mui/material/TextField';
8 import Select from '@mui/material/Select';
9 import MenuItem from '@mui/material/MenuItem';
10 import InputLabel from '@mui/material/InputLabel';
11 import Button from '@mui/material/Button';
12
13 export default function CreateHabitForm() {
14 const [name, setName] = React.useState("");
15 const [type, setType] = React.useState("morning");
16 const [alert, setAlert] = React.useState({message: null, type: null});
17
18 return(
19 <Grid container spacing={2} direction="column">
20 {alert.type && <Alert severity={alert.type}>{alert.message}</Alert>}
21 <Grid item>
22 <FormControl fullWidth>
23 <TextField
24 id="habit-name"
25 label="Name"
26 variant="outlined"
27 value={name}
28 onChange={(event) => setName(event.target.value)}/>
29 </FormControl>
30 </Grid>
31 <Grid item>
32 <FormControl fullWidth>
33 <InputLabel id="habit-project-select-type-label">Type</InputLabel>
34 <Select
35 labelId="habit-project-select-type-label"
36 id="habit-project-select-type"
37 value={type}
38 label="Type"
39 autoWidth
40 onChange={(event) => setType(event.target.value)}
41 >
42 <MenuItem value="morning">Morning</MenuItem>
43 <MenuItem value="afternoon">Afternoon</MenuItem>
44 <MenuItem value="evening">Evening</MenuItem>
45 </Select>
46 </FormControl>
47 </Grid>
48 <Grid item>
49 <Button variant="contained">Add</Button>
50 </Grid>
51 </Grid>
52 )
53 }
Inside your src
folder, create a file called HabitsList.js
. This component will receive a type (morning, afternoon, or evening) and will display a list of habits belonging to this type. Each habit comes with a checkbox that the user can tick off as the habit is completed.
To accomplish this, MUI has a few interesting components:
List
: is the entire list of habitsListItem
: has one for each habitListItemButton
: makes the ListItem
clickableListItemIcon
: renders the checkbox in-line and in front of your text in the ListItem
ListItemText
: is pretty explicit but, essentially, the text of your listWhen combined, you should have something like this:
1 import * as React from 'react';
2
3 import Grid from '@mui/material/Grid';
4 import Typography from '@mui/material/Typography';
5
6 import List from '@mui/material/List';
7 import ListItem from '@mui/material/ListItem';
8 import ListItemButton from '@mui/material/ListItemButton';
9 import ListItemIcon from '@mui/material/ListItemIcon';
10 import ListItemText from '@mui/material/ListItemText';
11
12 import Checkbox from '@mui/material/Checkbox';
13
14 export default function Habits({type}) {
15
16 const handleToggle = (value) => () => {};
17
18 return(
19 <Grid item>
20 {/* Title of the section aka Morning routine */}
21 <Typography variant="h6" gutterBottom component="div">
22 {type.replace(/^\w/, (c) => c.toUpperCase())} routine
23 </Typography>
24 {/* Whole list. You are using dummy data [0, 1, 2, 3] just for display */}
25 <List sx={{ width: '100%' }}>
26 {[0, 1, 2, 3].map((value) => {
27 const labelId = `checkbox-list-label-${value}`;
28 return (
29 <ListItem
30 key={value}
31 disablePadding
32 >
33 <ListItemButton role={undefined} onClick={handleToggle(value)} dense>
34 <ListItemIcon>
35 <Checkbox
36 edge="start"
37 checked={false}
38 tabIndex={-1}
39 disableRipple
40 inputProps={{ 'aria-labelledby': labelId }}
41 />
42 </ListItemIcon>
43 <ListItemText id={labelId} primary={`Line item ${value + 1}`} />
44 </ListItemButton>
45 </ListItem>
46 );
47 })}
48 </List>
49 </Grid>
50 )
51 }
Now, you need to create your section with your three types of habits. Inside the src
folder, create a file called Habits.js
. Inside this, you will receive calendarDate
as props from the parent and display it. You also need to import your newly created HabitsList
and create three lists: one for the morning, one for the afternoon, and one for the evening.
Following is the result:
1 import * as React from 'react';
2 import { format } from 'date-fns';
3
4 import Grid from '@mui/material/Grid';
5 import Typography from '@mui/material/Typography';
6
7 import HabitsList from './HabitsList';
8
9 export default function Habits({calendarDate}) {
10 return(
11 <>
12 <Typography variant="h5" gutterBottom component="div">
13 {format(calendarDate, "eeee, d LLLL yyyy")}
14 </Typography>
15 <Grid container spacing={2} direction="column">
16 <HabitsList type="morning" />
17 <HabitsList type="afternoon" />
18 <HabitsList type="evening" />
19 </Grid>
20 </>
21 )
22 }
With your App.js
file created, you can begin to piece together your frontend. All you need now is to import your new components—Habits
, CreateHabitForm
, and Calendar
—and set up your UI:
1 import * as React from 'react';
2
3 import Container from '@mui/material/Container';
4 import Grid from '@mui/material/Grid';
5 import Typography from '@mui/material/Typography';
6
7 import TextField from '@mui/material/TextField';
8
9 import Habits from './Habits';
10 import CreateHabitForm from './CreateHabitForm';
11 import Calendar from './Calendar';
12
13 export default function App() {
14 const [calendarDate, setCalendarDate] = React.useState(new Date());
15
16 return (
17 <Container maxWidth="md" style={{ paddingTop: '20px'}}>
18 {/* Main Container */}
19 <Grid container spacing={2} direction="column">
20 {/* Form + Calendar*/}
21 <Grid container spacing={2}>
22 <Grid item xs={6}>
23 <Typography variant="h4" component="div" gutterBottom>
24 Habit Picker project
25 </Typography>
26 <Typography variant="body1" gutterBottom>
27 Form
28 </Typography>
29 { /* Form to add habits */ }
30 <CreateHabitForm />
31 </Grid>
32 { /* Calendar */ }
33 <Calendar calendarDate={calendarDate} setCalendarDate={setCalendarDate} />
34 </Grid>
35 {/* Habits Table */}
36 <Habits calendarDate={calendarDate} />
37 </Grid>
38 </Container>
39 );
40 }
Save your files and start your frontend server. When launched, you should see the following at http://localhost:3000/:
Now that your backend and frontend are set up, it’s time to post and fetch data to and from your Strapi application.
The first things you need to do is head to your Strapi backend and navigate to Settings. Then under Users & Permissions Plugin, click on Roles > Public. Under Permissions, you will see your content type habit and habit-log. For both of them, click on the arrow to expand; then Select all > Save. This allows you to make HTTP requests from your frontend.
Note: If you get a
403 Forbidden
error when making calls, you may have missed a step.
Here’s how this step looks:
Now, go back to your frontend application and head to CreateHabitForm.js
.
Inside, import axios
to help create requests. Then add a handleSubmit
on your button, and in your function, make a POST request to create a new habit. If the request is successful, clean the form and display a success message. If not, display an error message.
The result looks like this:
1 import * as React from 'react';
2 import axios from "axios";
3
4 ...
5
6 export default function CreateHabitForm() {
7 ...
8
9 const handleSubmit = () => {
10 setAlert({ message: null, type: null})
11 axios
12 .post('http://localhost:1337/api/habits', {
13 data: {
14 name,
15 type
16 }
17 })
18 .then((response) => {
19 setName("")
20 setType("")
21 setAlert({ message: 'Habit created', type: 'success'})
22 })
23 .catch((error) => {
24 console.log(error);
25 setAlert({ message: 'An error occurred', type: 'error'})
26 });
27 }
28 return(
29 <Grid container spacing={2} direction="column">
30 {alert.type && <Alert severity={alert.type}>{alert.message}</Alert>}
31 ...
32 <Grid item>
33 <Button variant="contained" onClick={handleSubmit}>Add</Button>
34 </Grid>
35 </Grid>
36 )
37 }
In your Habits.js
component, pass the calendarDate
to your HabitsList
component. This is useful for knowing when a habit is completed. Here’s the code to use:
1 import * as React from 'react';
2 import { format } from 'date-fns';
3
4 import Grid from '@mui/material/Grid';
5 import Typography from '@mui/material/Typography';
6
7 import HabitsList from './HabitsList';
8
9 export default function Habits({calendarDate}) {
10 return(
11 <>
12 <Typography variant="h5" gutterBottom component="div">
13 {format(calendarDate, "eeee, d LLLL yyyy")}
14 </Typography>
15 <Grid container spacing={2} direction="column">
16 <HabitsList type="morning" calendarDate={calendarDate}/>
17 <HabitsList type="afternoon" calendarDate={calendarDate}/>
18 <HabitsList type="evening" calendarDate={calendarDate}/>
19 </Grid>
20 </>
21 )
22 }
Finally, in your HabitsList.js
component, create a habits
variable that includes your list of habits, which will be used instead of dummy data. To fetch your habits, use useEffect
so that upon rendering and calendarDate
changes, the component fetches the list from Strapi. The calendar date will be helpful in the future when you need the logs.
Strapi offers lots of cool filters, and you can do a comparison (equal, greater than, smaller than, etc.) on any field in your collection. In order to filter and only get habits of a particular type, you need to use qs
to create the query parameters to add to your URL:
1 const query = qs.stringify({
2 filters: {
3 type: {
4 $eq: type,
5 },
6 },
7 }, {
8 encodeValuesOnly: true,
9 });
Finally, create a completeHabit
function. This is added to the checkbox and creates a log for a particular habit on the date specified by the calendar. This means that if you forget to mark it as completed yesterday, you can select yesterday’s date on the calendar and check it off. You can use this code:
1 import * as React from 'react';
2 import axios from "axios";
3 import * as qs from 'qs'
4
5 import Grid from '@mui/material/Grid';
6 import Typography from '@mui/material/Typography';
7
8 import List from '@mui/material/List';
9 import ListItem from '@mui/material/ListItem';
10 import ListItemButton from '@mui/material/ListItemButton';
11 import ListItemIcon from '@mui/material/ListItemIcon';
12 import ListItemText from '@mui/material/ListItemText';
13
14 import Checkbox from '@mui/material/Checkbox';
15
16 export default function Habits({type, calendarDate}) {
17 const [habits, setHabits] = React.useState([]);
18
19 React.useEffect(() => {
20 const query = qs.stringify({
21 filters: {
22 type: {
23 $eq: type,
24 },
25 },
26 }, {
27 encodeValuesOnly: true,
28 });
29
30 axios.get(`http://localhost:1337/api/habits?${query}`)
31 .then((response) =>{
32 setHabits(response.data.data)
33 })
34 .catch((error) => console.log(error))
35 }, [calendarDate])
36
37 const completeHabit = (habitId) => () => {
38 axios
39 .post('http://localhost:1337/api/habit-logs', {
40 data: {
41 habit: habitId,
42 completionDate: calendarDate
43 }
44 })
45 .then((response) => {
46 console.log(response)
47 })
48 .catch((error) => {
49 console.log(error);
50 });
51 };
52
53 return(
54 <Grid item>
55 <Typography variant="h6" gutterBottom component="div">
56 {type.replace(/^\w/, (c) => c.toUpperCase())} routine
57 </Typography>
58 <List sx={{ width: '100%' }}>
59 {habits.map((habit) => {
60 const { id, attributes } = habit
61 const { name } = attributes
62 const labelId = `checkbox-list-label-${id}`;
63 return (
64 <ListItem
65 key={id}
66 disablePadding
67 >
68 <ListItemButton role={undefined} onClick={completeHabit(id)} dense>
69 <ListItemIcon>
70 <Checkbox
71 edge="start"
72 checked={false}
73 tabIndex={-1}
74 disableRipple
75 inputProps={{ 'aria-labelledby': labelId }}
76 />
77 </ListItemIcon>
78 <ListItemText id={labelId} primary={name} />
79 </ListItemButton>
80 </ListItem>
81 );
82 })}
83 </List>
84 </Grid>
85 )
86 }
At this point, you should be able to create new habits. When refreshing the page, the habits will appear in their corresponding list (ie your morning habits will be displayed under the Morning heading). If you click on a checkbox, a log will be created for that habit and on that date in your Strapi database, too. But how can you show that this habit has been completed?
This is the point where you could make a REST API endpoint to /habit-log
, retrieve the logs for a specific day, and see if the habit of the log matches the habit listed. However, this would be a lot of unnecessary logic. It’s easier if the habits are retrieved for a specific type and day, so Strapi sends this instead:
1 {
2 id: 1,
3 name: "Drink water",
4 type: "morning",
5 completed: true,
6 ...
7 }
This cannot be accomplished with REST, but you can use the Query Engine API.
Instead of interacting with the Admin UI, http://localhost:1337/admin/, of Strapi, in this tutorial, you’ll be getting your hands dirty in the code to implement a custom query.
To start, head to src/api/habit/routes
and create a new file custom-habits.js
. Inside this file, define a new custom endpoint called /get-habits-with-logs
and tell it to use the function getHabitWithLogs
in the controller custom-habit
:
1 'use strict';
2
3 module.exports = {
4 "routes": [
5 {
6 "method": "GET",
7 "path": "/get-habits-with-logs",
8 "handler": "custom-habit.getHabitWithLogs",
9 "config": {
10 "policies": []
11 }
12 },
13 ]
14 };
Then in src/api/habit/controllers
, create a new file called custom-habit.js
. Inside, you’ll add the getHabitWithLogs
function.
The first thing you need are the calendarDate
and type
values, which come as query parameters from your request. You can get them from ctx.request.query
.
Then create a query for all the habits of that particular type. Once you have all the habits, you need to make a second query for each habit to the habit-log
type. Here, you fetch all the logs where the ID of the habit on the log matches the habits you just retrieved. The completionDate
will also match the calendarDate
requested. If the ID and the date match, that means that the habit was marked as completed for that date. Then you can add a new key called completed
on your habit object and pass it as true
or false
depending on whether the list of logs was empty or not.
Note: You need to make sure that your query for the logs is async and inside a
map()
. This means thathabits_with_completed
will return a list of promises. To execute them, you need to useawait Promise.all(habits_with_completed);
.
Here is the result:
1 'use strict';
2
3 const { createCoreController } = require('@strapi/strapi').factories;
4 const habitModel = "api::habit.habit";
5 const habitLogModel = 'api::habit-log.habit-log';
6
7 module.exports = createCoreController(habitModel, ({ strapi }) => ({
8 async getHabitWithLogs(ctx, next) {
9 const { calendarDate, type } = ctx.request.query
10 try {
11 // Query all the habits of a particular type (say morning)
12 const habits = await strapi.db.query(habitModel).findMany({
13 where: {
14 type: {
15 $eq: type,
16 }
17 },
18 });
19
20 const habits_with_completed = habits.map(async (habit, i) => {
21 // For each habit, query the logs
22 // to see if there were completed that day
23 let entries_logs = await strapi.db.query(habitLogModel).findMany({
24 where: {
25 $and: [
26 {
27 habit: habit.id,
28 },
29 {
30 completionDate: { $eq: calendarDate },
31 },
32 ],
33 },
34 });
35 // Add a completed key based on the logs found for that day
36 return {
37 completed: entries_logs.length > 0,
38 ...habit
39 }
40 });
41 ctx.body = await Promise.all(habits_with_completed);
42 } catch (err) {
43 ctx.body = err;
44 }
45 }
46 }));
Once your file is saved, your Strapi server will restart with your new endpoint. Don’t forget to enable permissions for the endpoint by going back to Settings > Roles > Public and selecting all for custom-habit, too. Otherwise, you’ll get the 403 Forbidden
error.
Here’s how to enable the new endpoint:
Now, call your new endpoint in HabitsList.js
. In your query parameters, pass the formatted date and the type with calendarDate=${format(calendarDate, "yyyy-MM-dd")}&type=${type}
. Then retrieve id
, name
, and completed
; and use the latter to set the checkbox correctly.
You should also move the fetching of the data into its own function called fetchData()
so when you complete a habit, you can refetch the data so the list is up-to-date:
1 import * as React from 'react';
2 import axios from "axios";
3 import * as qs from 'qs'
4 import { format } from 'date-fns';
5
6 import Grid from '@mui/material/Grid';
7 import Typography from '@mui/material/Typography';
8
9 import List from '@mui/material/List';
10 import ListItem from '@mui/material/ListItem';
11 import ListItemButton from '@mui/material/ListItemButton';
12 import ListItemIcon from '@mui/material/ListItemIcon';
13 import ListItemText from '@mui/material/ListItemText';
14
15 import Checkbox from '@mui/material/Checkbox';
16
17 export default function Habits({type, calendarDate}) {
18 const [habits, setHabits] = React.useState([]);
19
20 React.useEffect(() => {
21 fetchData()
22 }, [calendarDate])
23
24 const fetchData = () => {
25 axios.get(`http://localhost:1337/api/get-habits-with-logs?calendarDate=${format(calendarDate, "yyyy-MM-dd")}&type=${type}`)
26 .then((response) =>{
27 setHabits(response.data)
28 })
29 .catch((error) => console.log(error))
30 }
31
32 const completeHabit = (habitId) => () => {
33 axios
34 .post('http://localhost:1337/api/habit-logs', {
35 data: {
36 habit: habitId,
37 completionDate: calendarDate
38 }
39 })
40 .then((response) => {
41 fetchData()
42 })
43 .catch((error) => {
44 console.log(error);
45 });
46 };
47
48 return(
49 <Grid item>
50 <Typography variant="h6" gutterBottom component="div">
51 {type.replace(/^\w/, (c) => c.toUpperCase())} routine
52 </Typography>
53 <List sx={{ width: '100%' }}>
54 {habits.length > 0 && habits.map((habit) => {
55 const { id, name, completed } = habit
56 const labelId = `checkbox-list-label-${id}`;
57 return (
58 <ListItem
59 key={id}
60 disablePadding
61 >
62 <ListItemButton role={undefined} onClick={completeHabit(id)} dense>
63 <ListItemIcon>
64 <Checkbox
65 edge="start"
66 checked={completed}
67 tabIndex={-1}
68 disableRipple
69 inputProps={{ 'aria-labelledby': labelId }}
70 />
71 </ListItemIcon>
72 <ListItemText id={labelId} primary={name} />
73 </ListItemButton>
74 </ListItem>
75 );
76 })}
77 </List>
78 </Grid>
79 )
80 }
Following is a GIF of your habit tracker in action:
If you want to clone the project and follow along in your own editor, use this GitHub repo.
Selecting a flexible and robust backend is essential when creating a personal habit tracker app, and Strapi offers a range of features that make it an excellent choice.
Strapi is an open-source headless CMS built with Node.js. It allows you to manage and deliver content through customizable APIs. With Strapi, you can quickly set up content types, such as "Habit" and "HabitLog," and automatically generate RESTful or GraphQL endpoints for them. The intuitive admin panel simplifies content management with Strapi, enabling you to define data models, set up relationships, and manage permissions easily.
One of Strapi's strengths is its ability to be customized to meet specific needs. You can extend its default functionality by creating custom controllers, services, and database queries. Such customization is particularly useful for a habit tracker app, where features like calculating habit completion rates, tracking streaks, or generating personalized insights can be implemented.
By using Strapi's customization capabilities, you can build a habit tracker that goes beyond basic CRUD operations and offers a more engaging experience for users.
Strapi has a growing community of developers and a wealth of resources available, helping you in boosting productivity with Strapi. Numerous tutorials and articles guide you through building applications, including habit trackers. Strapi's plugin system allows you to extend the platform's functionality by integrating existing plugins or developing custom ones if needed.
Building a personal habit tracker app with Strapi demonstrates the power and flexibility of this headless CMS. By using Strapi's robust features and customization options, you've created a sophisticated application tailored to your needs. As you continue to develop your application and explore new possibilities, remember that Strapi offers a variety of plans to support your business needs. Discover the plan that suits your business needs today.
Marie Starck is a full-stack software developer. She loves frontend technologies such as React. In her free time, she also writes tech tutorials. if she could, she would get paid in chocolate bars.