Simply copy and paste the following command line in your terminal to create your first Strapi project.
npx create-strapi-app
my-project
Our community is looking for talented writers who are passionate about our ecosystem (jamstack, open-source, javascript) and willing to share their knowledge/experiences through our Write for the community program.
There's a whole series in blog of tutorials on how to make blogs using Strapi with a lot of frontend frameworks: Gatsby, React, Next.js, Vue, Nuxt or Angular.
This time we are going to create a Gatsby blog using TypeScript, and adding one of coolest plugins in the ecosystem: gatsby-image.
Caveat emptor: this is a re write of the previous tutorial about creating a blog with STRAPI + Gatsby. So the design will be the same.
The goal here is to be able to create a simple static blog website using Strapi as the backend and Gatsby for the frontend, using TypeScript.
The source code is available on GitHub.
This tutorial uses Strapi v3.0.0-beta.20.3.
You need to have node v.12 installed and that's all.
Create a strapi-gatsby-typescript-blog
folder and get inside!
mkdir strapi-gatsby-typescript-blog && cd strapi-gatsby-typescript-blog
That's the easiest part, as since beta.9 Strapi has an excellent package create strapi-app that allows you to create a Strapi project in seconds without needing to install Strapi globally so let's try it out.
Note: for this tutorial, we will use yarn
as your package manager.
yarn create strapi-app backend --quickstart --no-run
.This single command line will create all you need for your back-end. Make sure to add the --no-run
flag as it will prevent your app from automatically starting the server because SPOILER ALERT: we need to install some awesome Strapi plugins.
Now that you know that we need to install some plugins to enhance your app, let's install one of our most popular ones: the graphql
plugin.
yarn strapi install graphql
Once the installation is completed, you can finally start your Strapi server strapi dev
and create your first Administrator.
Don't forget that Strapi is running on http://localhost:1337
Nice! Now that Strapi is ready, you are going to create your Gatsby application.
The easiest part has been completed, let's get our hands dirty developing our blog with Gatsby!
Gatsby setup
First of all, you'll need to install the Gatsby CLI
Install the Gatsby CLI by running the following command:
npm install -g gatsby-cli
Create a Gatsby frontend
project by running the following command:
gatsby new frontend https://github.com/resir014/gatsby-starter-typescript-plus
Once the installation is completed, you can start your front-end app to make sure everything went ok.
1
2
cd frontend
gatsby develop
Good start!
A little note about the starter: as you can see, we didn't use the default Gatsby starter repo, we used a TypeScript starter, so we don't have to add it ourselves ;)
We are not keeping the default Gatsby starter style but the one we used for every other tutorials of this series.
UIkit setup
We need to create a Seo
component that will allow us to add UIkit cdn and Staatliches font
components/Seo.tsx
(mind the file extension, TypeScript React/Gatsby files are .tsx) and add this code:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import React from 'react'
import PropTypes from 'prop-types'
import { Helmet } from 'react-helmet'
import { useStaticQuery, StaticQuery, graphql } from 'gatsby'
interface StaticQueryProps {
site: {
siteMetadata: {
title: string
description: string
author: string
keywords: string
}
}
}
interface Props {
readonly title?: string
readonly children?: React.ReactNode
}
const SEO: React.FC<Props> = ({ children }) => (
<StaticQuery
query={graphql`
query SeoQuery {
site {
siteMetadata {
title
description
}
}
}
`}
render={(data: StaticQueryProps) => (
<Helmet
htmlAttributes={{
lang: `en`
}}
title={data.site.siteMetadata.title}
titleTemplate={`%s | ${data.site.siteMetadata.title}`}
link={[
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css?family=Staatliches'
},
{
rel: 'stylesheet',
href: 'https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/css/uikit.min.css'
}
]}
script={[
{
src: 'https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.min.js'
},
{
src: 'https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/js/uikit-icons.min.js'
},
{
src: 'https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.js'
}
]}
meta={[
{
name: `description`,
content: data.site.siteMetadata.description
},
{
property: `og:title`,
content: data.site.siteMetadata.title
},
{
property: `og:description`,
content: data.site.siteMetadata.description
},
{
property: `og:type`,
content: `website`
},
{
name: `twitter:card`,
content: `summary`
},
{
name: `twitter:creator`,
content: 'kilinkis'
},
{
name: `twitter:title`,
content: data.site.siteMetadata.title
},
{
name: `twitter:description`,
content: data.site.siteMetadata.description
}
]}
/>
)}
/>
)
export default SEO
Let's explain that code a little bit:
Here we are using the React Helmet library (fun fact: it was created by the NFL devs). It handles the <head>
component of our app. In this case, we import UIkit from a CDN, also the font and handle metadescriptions.
We also define two interfaces we need in order to use our components, these are StaticQueryProps
and Props
.
StaticQueryProps
describes the object returned in the query that fetches the site description.
Props
is very simple, it describes the kind of Props that the React Functional Component below expects.
We'll talk more about StaticQuery
later on when we fetch our articles :)
In order to have a simple but beautiful blog we're going to add some style:
src/styles/main.ts
file containing the following:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
export default `
a {
text-decoration: none !important;
}
h1 {
font-family: Staatliches !important;
font-size: 120px !important;
}
#category {
font-family: Staatliches !important;
font-weight: 500 !important;
}
#title {
letter-spacing: 0.4px !important;
font-size: 22px !important;
font-size: 1.375rem !important;
line-height: 1.13636 !important;
}
#banner {
margin: 20px !important;
height: 800px !important;
}
#editor {
font-size: 16px !important;
font-size: 1rem !important;
line-height: 1.75 !important;
}
.uk-navbar-container {
background: #fff !important;
font-family: Staatliches !important;
}
img:hover {
opacity: 1 !important;
transition: opacity 0.25s cubic-bezier(0.39, 0.575, 0.565, 1) !important;
}
.banner-bg {
position: absolute !important;
width: calc(100% - 2.5rem) !important;
height: 800px !important;
}
`
We are not going to explain that CSS, but feel free to change anything as you need :)
Now we need to include this file in out layout wrapper in order to make it available to our app:
src/components/LayoutRoot.tsx
and add the import after Normalize, like so:import main from '../styles/main'
and finally, add our freshly imported main
CSS to the Global component:
<Global styles={() => css(normalize, main)} />
Nice! Let's continue.
Strapi Setup
To connect Gatsby to a new source of data, you have to develop a new source plugin. Fortunately, several source plugins already exist, so one of them could fill your needs.
In this example, we are using Strapi as the backend provider, so we need a source plugin for Strapi APIs. Good news: we built it for you!
gatsby-source-strapi
by running the following command:yarn add gatsby-source-strapi
This plugin needs to be configured.
gatsby-config.js
with the following code:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
'use strict'
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`
})
module.exports = {
siteMetadata: {
title: 'My super blog',
description: 'Gatsby blog with Strapi',
author: {
name: 'Strapi team',
url: 'https://strapi.io/'
}
},
plugins: [
{
resolve: 'gatsby-source-strapi',
options: {
apiURL: process.env.API_URL || 'http://localhost:1337',
collectionTypes: [
// List of the Content Types you want to be able to request from Gatsby.
'article',
'category'
],
queryLimit: 1000
}
},
'gatsby-transformer-json',
{
resolve: 'gatsby-plugin-canonical-urls',
options: {
siteUrl: 'https://gatsby-starter-typescript-plus.netlify.com'
}
},
'gatsby-plugin-emotion',
'gatsby-plugin-typescript',
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
'gatsby-plugin-react-helmet'
]
}
As you can see at the top of that code, we are using environment variables for our API url. So, create a .env.development
file a the root of your frontend folder containing the following:
API_URL="http://localhost:1337"
Important note that we defined our API_URL
for the Strapi API: http://localhost:1337
without a trailing slash and that we plan to query content types article and category
Alright! Gatsby is now ready to fetch data from Strapi, but first, we need to create some data in Strapi!
Finally! We are now going to create the data structure of our article by creating an Article
content type.
Content-Types Builder
link in the sidebar.Create new collection type
and call it article
.Now you'll be asked to create all the fields for your collection-type:
title
with type Text (required)content
with type Rich Text (required)image
with type Media (Single image) and (required)published_at
with type date (required)Press Save! Here you go, your first content type has been created. Now you may want to create your first article, but we have one thing to do before that: Grant access to the article content type.
public
role.find
and findone
routes and save.Awesome! You should be ready to create your first article right now and fetch it on the GraphQL Playground.
Now, create your first article !
Here's an example:
Great! Now you may want to reach the moment when you can actually fetch your articles through the API!
Go to http://localhost:1337/articles.
Isn't that cool! You can also play with the GraphQL Playground.
You may want to assign a category to your article (news, trends, opinion). You are going to do this by creating another collection type in Strapi.
Create a new Collection Type, named category
, with a Text field named category and a Relation field, that has Many to One relationship.
Press save!
Category has many Articles
like below:Click on the Settings then Roles & Permission and click on the public
role. And check the category find
and findone
routes and save.
Now you'll be able to select a category for your article in the right sidebox.
Now that we are good with Strapi let's work on the frontend part!
Before we can dive in, we have to clean the default Gatsby architecture by removing useless files with the following command:
rm src/pages/page.tsx src/pages/page-2.tsx
Since we are using TypeScript in this project, we are writting type safe code, and for that we are using Interfaces in most cases. To avoid duplicated code, we can declare interfaces in the typings.d.ts
file.
In this case, lets declare our Article interface so we can use it across our app.
typings.d.ts
file:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Article {
node: {
id: number
strapiId: string
image: {
childImageSharp: {
fixed: FixedObject
fluid: FluidObject
}
}
category: {
name: string
}
title: string
content: string
}
}
Now we need to update the layout/index.tsx
file. Replace its content with the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import * as React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import 'modern-normalize'
import '../styles/normalize'
import LayoutRoot from '../components/LayoutRoot'
import ArticlesComponent from '../components/Articles'
interface StaticQueryProps {
site: {
siteMetadata: {
title: string
description: string
keywords: string
}
}
allStrapiArticle: {
edges: Article[]
}
}
interface Props {
readonly title?: string
readonly children: React.ReactNode
}
const IndexLayout: React.FC<Props> = ({ children }) => (
<StaticQuery
query={graphql`
query IndexLayoutQuery {
site {
siteMetadata {
title
description
}
}
allStrapiArticle {
edges {
node {
strapiId
title
category {
name
}
image {
childImageSharp {
fluid(maxWidth: 595, quality: 100) {
...GatsbyImageSharpFluid
...GatsbyImageSharpFluidLimitPresentationSize
}
}
}
}
}
}
}
`}
render={(data: StaticQueryProps) => (
<LayoutRoot>
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>Strapi blog</h1>
<ArticlesComponent articles={data.allStrapiArticle.edges} />
</div>
</div>
</LayoutRoot>
)}
/>
)
export default IndexLayout
What is going on here ? Let me explain
Gatsby v2 introduces StaticQuery
, a new API that allows components to retrieve data via a GraphQL query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
allStrapiArticle {
edges {
node {
strapiId
title
category {
name
}
image {
childImageSharp {
fluid(maxWidth: 595, quality: 100) {
...GatsbyImageSharpFluid
...GatsbyImageSharpFluidLimitPresentationSize
}
}
}
}
}
}
This one will fetch all your articles from Strapi. Here, for each article we are querying: the id, title, category and image. The image object, as you can see, it's a little complex. This is because we are using gatsby-image here. This is one of the most popular plugins in Gatsby, mainly because it fixes these common problems virtually for free (taken from their docs):
I use it in every Gatsby project, I just think it helps in our endeavour of making the web a better place. You can read more about it and its API on the offical docs.
Then StaticQuery
render your html with the fetched data:
1
2
3
4
5
6
7
8
9
10
render={(data: StaticQueryProps) => (
<LayoutRoot>
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>Strapi blog</h1>
<ArticlesComponent articles={data.allStrapiArticle.edges} />
</div>
</div>
</LayoutRoot>
)}
Now we need to update the components/Header.tsx
file. Replace its content with the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import * as React from 'react'
import { Link, StaticQuery, graphql } from 'gatsby'
interface categoryInterface {
node: {
strapiId: number
name: string
}
}
interface HeaderProps {
title?: string
}
const Header: React.FC<HeaderProps> = ({ title }) => (
<div>
<div>
<nav className="uk-navbar-container" data-uk-navbar>
<div className="uk-navbar-left">
<ul className="uk-navbar-nav">
<li>
<Link to="/">Strapi Blog</Link>
</li>
</ul>
</div>
<div className="uk-navbar-right">
<ul className="uk-navbar-nav">
<StaticQuery
query={graphql`
query {
allStrapiCategory {
edges {
node {
strapiId
name
}
}
}
}
`}
render={data =>
data.allStrapiCategory.edges.map((category: categoryInterface, i: number) => {
return (
<li key={category.node.strapiId}>
<Link to={`/category/${category.node.strapiId}`}>{category.node.name}</Link>
</li>
)
})
}
/>
</ul>
</div>
</nav>
</div>
</div>
)
export default Header
There's not so much to explain here, but basically we add the two new Interfaces we need: categoryInterface
and HeaderProps
.
Then StaticQuery
gets all the categories we defined in our backend.
1
2
3
4
5
6
7
8
allStrapiCategory {
edges {
node {
strapiId
name
}
}
}
And loops through them to create the Catergory navegation.
1
2
3
4
5
6
7
8
9
render={data =>
data.allStrapiCategory.edges.map((category: categoryInterface, i: number) => {
return (
<li key={category.node.strapiId}>
<Link to={`/category/${category.node.strapiId}`}>{category.node.name}</Link>
</li>
)
})
}
Now we need to use this component in your Layout
component.
Header
component by pasting the following code inside your components/LayoutRoot.tsx
file:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import * as React from 'react'
import Seo from './Seo'
import Header from './Header'
import { Global, css } from '@emotion/core'
import normalize from '../styles/normalize'
import main from '../styles/main'
interface LayoutRootProps {
className?: string
}
const LayoutRoot: React.FC<LayoutRootProps> = ({ children, className }) => (
<>
<Seo />
<Global styles={() => css(normalize, main)} />
<Header />
<main>{children}</main>
</>
)
export default LayoutRoot
Also, we need to update the content of our index page, editing the content of ./pages.index.tsx
. Replace its content with this:
1
2
3
4
5
6
7
8
9
10
11
12
import * as React from 'react'
import Page from '../components/Page'
import IndexLayout from '../layouts'
const IndexPage = () => (
<IndexLayout>
<Page></Page>
</IndexLayout>
)
export default IndexPage
At this point we can show the navigation header and the empty landing page, but wait... not so fast, if we try to compile the project now, it will fail because our gatsby-node file
is outdated. We'll add the correct code there soon.
Note: The current code is not designed to display a lot of categories as one would encounter in a typical blog.
Great! But we still need to create some extra components we added in our main layout file.
components/articles.tsx
containing the following code:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from 'react'
import Card from './Card'
interface ArticlesProps {
articles: Article[]
}
const Articles: React.FC<ArticlesProps> = ({ articles }) => {
const leftArticlesCount = Math.ceil(articles.length / 5)
const leftArticles = articles.slice(0, leftArticlesCount)
const rightArticles = articles.slice(leftArticlesCount, articles.length)
return (
<div>
<div className="uk-child-width-1-2" data-uk-grid>
<div>
{leftArticles.map(article => {
return <Card article={article} key={`article__${article.node.id}`} />
})}
</div>
<div>
<div className="uk-child-width-1-2@m uk-grid-match" data-uk-grid>
{rightArticles.map(article => {
return <Card article={article} key={`article__${article.node.id}`} />
})}
</div>
</div>
</div>
</div>
)
}
export default Articles
Here we are splitting our articles because we want to display some of them on the left side of the blog making them bigger and some on the right making them smaller. It's just a design thing.
Again, we are using another component: Card
. Since Components are the building blocks of any React application, this is why you will find so many of them.
The benefit to this approach is that you end up doing less duplicate work by managing the components rather than managing duplicate content across different pages. This concept has become a real success and you are going to use it too!
components/card.js
file containing the following code:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from 'react'
import { Link } from 'gatsby'
import Img, { FixedObject } from 'gatsby-image'
interface ArticleProps {
article: Article
}
const Card: React.FC<ArticleProps> = ({ article }) => {
return (
<Link to={`/article/${article.node.strapiId}`} className="uk-link-reset">
<div className="uk-card uk-card-muted">
<div className="uk-card-media-top">
<Img fluid={article.node.image.childImageSharp.fluid} />
</div>
<div className="uk-card-body">
<p id="category" className="uk-text-uppercase">
{article.node.category.name}
</p>
<p id="title" className="uk-text-large">
{article.node.title}
</p>
</div>
</div>
</Link>
)
}
export default Card
Awesome! Now let's updade the gatsby.node.js
file to create the article and category pages and finish the tutorial :)
How can we create a page for each one of your article now? We'll need to use the createPages
API. It will be called once the data layer is bootstrapped to let plugins create pages from data.
gatsby.node.js
file:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
'use strict'
const path = require('path')
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(
`
{
articles: allStrapiArticle {
edges {
node {
strapiId
}
}
}
categories: allStrapiCategory {
edges {
node {
strapiId
}
}
}
}
`
)
if (result.errors) {
throw result.errors
}
// Create blog articles pages.
const articles = result.data.articles.edges
const categories = result.data.categories.edges
articles.forEach((article, index) => {
createPage({
path: `/article/${article.node.strapiId}`,
component: require.resolve('./src/templates/article.tsx'),
context: {
id: article.node.strapiId
}
})
})
categories.forEach((category, index) => {
createPage({
path: `/category/${category.node.strapiId}`,
component: require.resolve('./src/templates/category.tsx'),
context: {
id: category.node.strapiId
}
})
})
}
We are fetching all your article and category ids in order to generate pages for each one of them
It means that if you have 4 articles it will create 4 pages depending on the path you give, here it's path: /article/${article.node.strapiId}
:
1
2
3
4
/article/1
/article/2
/article/3
/article/4
Just to keep it short, in that same step we added the node loop to create the category
page. We'll work on that template soon.
Before going further you'll need to install two packages: one to display your content as Markdown and Moment
react-markdown
and react-moment
by running the following command:yarn add react-markdown react-moment moment
Now we need to create the page that will display each one of your article, you can see inside gatsby.node.js
that here it's component: require.resolve("./src/templates/article.tsx")
src/templates/article.tsx
file containing the following:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import React from 'react'
import { graphql } from 'gatsby'
import Img, { FixedObject } from 'gatsby-image'
import ReactMarkdown from 'react-markdown'
import LayoutRoot from '../components/LayoutRoot'
interface ArticleProps {
data: {
strapiArticle: {
image: {
childImageSharp: {
fixed: FixedObject
}
}
title: string
content: string
}
}
}
export const query = graphql`
query ArticleQuery($id: Int!) {
strapiArticle(strapiId: { eq: $id }) {
strapiId
title
content
published_at
image {
childImageSharp {
fixed(width: 660) {
...GatsbyImageSharpFixed
}
}
}
}
}
`
const Article: React.FC<ArticleProps> = ({ data }) => {
const article = data.strapiArticle
return (
<LayoutRoot>
<div>
<div
id="banner"
className="uk-height-medium uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding uk-margin"
>
<Img className="banner-bg" fixed={article.image.childImageSharp.fixed} />
<h1 className="uk-position-z-index">{article.title}</h1>
</div>
<div className="uk-section">
<div className="uk-container uk-container-small">
<ReactMarkdown source={article.content} />
</div>
</div>
</div>
</LayoutRoot>
)
}
export default Article
Remember we already added the category
code in our gatsby.node.js
file already? Okay, now we are going to use that.
We need to create the category
template, as required here component: require.resolve("./src/templates/category.tsx")
src/templates/category.tsx
file containing the following code:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import React from 'react'
import { graphql } from 'gatsby'
import LayoutRoot from '../components/LayoutRoot'
import ArticlesComponent from '../components/articles'
interface CategoryProps {
data: {
articles: {
edges: Article[]
}
category: {
name: string
}
}
}
export const query = graphql`
query Category($id: Int!) {
articles: allStrapiArticle(filter: { category: { id: { eq: $id } } }) {
edges {
node {
strapiId
title
category {
name
}
image {
childImageSharp {
fluid(maxWidth: 595, quality: 100) {
...GatsbyImageSharpFluid
...GatsbyImageSharpFluidLimitPresentationSize
}
}
}
}
}
}
category: strapiCategory(strapiId: { eq: $id }) {
name
}
}
`
const Category: React.FC<CategoryProps> = ({ data }) => {
const articles = data.articles.edges
const category = data.category.name
return (
<LayoutRoot>
<div className="uk-section">
<div className="uk-container uk-container-large">
<h1>{category}</h1>
<ArticlesComponent articles={articles} />
</div>
</div>
</LayoutRoot>
)
}
export default Category
You can see that we are using the ArticlesComponent
again! In fact we are displaying articles the same we as in the index, this is why we created a component, in order to not duplicate code ;)
gatsby.node.js
file)Huge congrats, you successfully achieved this tutorial. I hope you enjoyed it!
Still hungry?
Feel free to add additional features, adapt this project to your own needs, and give your feedback in the comment section below.
If you want to deploy your application, check our documentation.
Contribute and collaborate on educational content for the Strapi Community https://strapi.io/write-for-the-community
Can't wait to see your contribution!
One last thing, we are trying to make the best possible tutorials for you, help us in this mission by answering this short survey https://strapisolutions.typeform.com/to/bwXvhA?channel=xxxxx
Please note: Since we initially published this blog, we released new versions of Strapi and tutorials may be outdated. Sorry for the inconvenience if it's the case, and please help us by reporting it here.
Get started with Strapi by creating a project, using a starter or trying our instant live demo. Also, consult our forum if you have any questions. We will be there to help you.
See you soon!