DEV Community

Dev Kadiem
Dev Kadiem

Posted on • Originally published at Medium on

Typescript, NextJS, Strapi, and GraphQL: An Overview Of How I Created My Blog

First things first, let's introduce the stack we are going to use. Typescript is "Is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale." in simple words, Typescript enhances coding safety and enables more OOP for the web.

NextJS is a framework that "Enables you to create full-stack web applications by extending the latest React features" in simple words, NextJS is an upgrade over the widely used ReactJS library.

Strapi is an open-source headless CMS. CMS stands for "content management system," allowing us to create, manage, modify, and publish content in a user-friendly interface without programming knowledge. Headless means there is no built-in end-user UI; we can use whatever frontend technology we want.

GraphQL is "A query language for APIs and a runtime for fulfilling those queries with your existing data" in simple words, it's a language used to query data and get the fields you want.

Note: this is an overview, which means there are aspects I will not go over, such as styling and best HTML tags; this blog is meant to showcase the functionality of creating a Blog using the technologies mentioned above.

Initializing The Project

Now, let's create the folders using the command line (because it's cooler), so the first command is to create a folder containing all our files.

mkdir my-personal-website
Enter fullscreen mode Exit fullscreen mode

Now, let's create folders for both NextJS and Strapi.

cd my-personal-website
mkdir frontend, strapi
Enter fullscreen mode Exit fullscreen mode

Initialize Strapi

cd strapi
npx create-strapi-app@latest . --ts --quickstart
Enter fullscreen mode Exit fullscreen mode

Parameter @latest means it will install the latest version, . means we want to create the project in the current folder, then — ts means it will use TypeScript, and lastly — quickstart it will install with default settings; when the installation finishes, it will open a new window on the address /admin/auth/register-admin, asking you to create a new admin account; go ahead and create a new account. Note: using — ts is optional since I won't change the source code in this post.

Initialize NextJS

cd frontend
npx create-next-app@latest . --ts
Enter fullscreen mode Exit fullscreen mode

And lastly, remember to init a Github repo, but I'll not cover it in this post.

Starting with Strapi

I like to start with the backend in my projects, so I will start with Strapi as it's considered a backend.

Installing Graphql

One advantage of using Strapi is its easy integration with Graphql. To install the Graphql plugin, we need the run the following command.

npm run strapi install graphql
Enter fullscreen mode Exit fullscreen mode

If you encounter the _Duplicate "graphql" modules error, don't worry; it's easy to fix; all you need to do is to delete the node_modules folder and package-lock.json , then we need to reinstall packages.

rm node_modules
rm package-lock.json
npm install
npm run strapi dev
Enter fullscreen mode Exit fullscreen mode

After successful installation, we can now visit the URL /graphql.

Creating The Blog Post Collection

Open the Strapi homepage on URL http://localhost:1337/admin/, and after you sign in, go to the Content-Type Builder page, click on Create new collection type button, then in the Display name field, fill it in with an appropriate name; I will choose Blog Post , then click continue.

Now, we are in fields selection for our new collection; you can customize it however you want; for the sake of simplicity, I will add three fields, a Text field for the title, another Text field for the description, and lastly, a Rich Text field for the body, next click save.

Now that we have a collection, we need to populate it with data, go to Content Manager , and now we can see the newly created collection, click on it, then Create new entry button, and fill it with any data you want, then click save and publish.

Accessibility Option 1: Publicly Accessible

Before we can access our data through Graphql, we need to make it accessible; you can make it publicly accessible or only for authenticated users. I will choose a publicly accessible collection since I'm not storing sensitive data.

To access it publicly, go to Settings, then roles, click on Public , scroll down to the collection you created (in my case, it's Blog-post), click it, then tick find and findOne.

Accessibility Option 2: Authenticated Only

However, should you choose the authenticated way, here is how to do it. There are two ways to achieve this; the first one is to create API Token in the settings page, then we need to attach it with our requests by adding it to the headers as an Authorization header and setting the value to be bearer , the second way is to create a new entry in the User collection, via Content Manager page, then send a Post request to /api/auth/local with an attached JSON body containing identifier which could be either the username or email, and password, of the newly created user in the User collection, NOT THE ADMIN ACCOUNT WE CREATED IN INITIALIZE STRAPI.

{
 "identifier": "email or username",
 "password": "password"
}
Enter fullscreen mode Exit fullscreen mode

If the request were successful, you would get a response containing the JWT field, and now we need to do the same thing, send it with each request via headers in an Authorization header and set the value to be bearer .

Now to access it, go to Settings, then roles, click on Authenticated , scroll down to the collection you created (in my case, it's Blog-post), click it, then tick find and findOne.

Testing Graphql Endpoint

To access Graphql go to /graphql, and now, we can see the input field on the left side; here, we will put our queries to query our Strapi application, an example of a query:

query {
  blogPosts {
    data {
      id
      attributes {
        title
        description
        body
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

And in my case, the response is:

  "data": {
    "blogPosts": {
      "data": [
        {
          "id": "1",
          "attributes": {
            "title": "test",
            "description": "test",
            "body": "test"
          }
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Testing Rest Endpoint

All we need to do is send a GET request to /api/blog-posts/. An example of a response would be:

{
    "data": [
        {
            "id": 1,
            "attributes": {
                "title": "test",
                "description": "test",
                "body": "test",
                "createdAt": "2023-01-30T20:28:28.141Z",
                "updatedAt": "2023-01-30T20:28:29.027Z",
                "publishedAt": "2023-01-30T20:28:29.025Z"
            }
        }
    ],
    "meta": {
        "pagination": {
            "page": 1,
            "pageSize": 25,
            "pageCount": 1,
            "total": 1
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We have finished the Strapi section and will continue with NextJS.

Starting With NextJS

Create a new directory inside the pages directory and name it blog ; next, create a new index.tsx file and initialize a basic react component.

import {NextPage} from "next";

interface Props {
}

const About: NextPage<Props> = (Props) => {
    return (
        <p>About Page</p>
    )
}

export default About;
Enter fullscreen mode Exit fullscreen mode

Now we are to visit blog and view the About Page.

Creating Interfaces

We will need a few interfaces; let's start by creating a common folder containing all of our basic modules, then create another folder called types which will contain all our types and interfaces then create a new typescript I will name BlogInterfaces ; now let the first interface, which is going to IBlogIdentification and it will contain our identification field; in our case, it's the id of the blog post.

export interface IBlogIdentification {
    id: string
}
Enter fullscreen mode Exit fullscreen mode

Next, we need an interface to hold the blog fields' title, description, and body; name it IBlogFields.

export interface IBlogFields {
    title: string,
    description: string,
    body: string
}
Enter fullscreen mode Exit fullscreen mode

Next, we need an interface for blog attributes we will receive from the GraphQL query; name it IBlogAttributes.

export interface IBlogAttributes {
    attributes: Partial<IBlogFields> & Pick<IBlogFields, 'title'>
}
Enter fullscreen mode Exit fullscreen mode

I have used Partial and Pick utility types to make all of IBlogFields fields optional except title.

Lastly, we will need an interface to handle the whole blog entry; name it IBlog.

export interface IBlog extends IBlogIdentification, IBlogAttributes{}
Enter fullscreen mode Exit fullscreen mode

Now let's update the Props interface on the blog page.

interface Props {
    blogs: IBlog[]
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Works

Before we start fetching data, we need to do GraphQL Works. First, we need to install two packages to be able to interact with GraphQL; we will need the graphql and @apollo/client packages.

npm i graphql, @apollo/client
Enter fullscreen mode Exit fullscreen mode

Second, we need to write the queries; we will need three queries, the first to list blogs, the second is to get a single blog, and the third is to get the list of blog Ids'; in the common folder, create a new folder and call it graphql, then create a new file and call it queries.tsx, and add the following queries.

export const LIST_BLOG = gql(`query {
  blogPosts {
    data {
      id
      attributes {
        title
      }
    }
  }
}`)

export const SINGLE_BLOG = gql(`query ($blogId: string!) {
  blogPosts(filters: {id: {eq: $blogId}}) {
    data {
      id
      attributes {
        title
        description
        body
      }
    }
  }
}`)

export const LIST_ID = gql(`query {
  blogPosts {
    data {
      id
    }
  }
}`)
Enter fullscreen mode Exit fullscreen mode

The third thing we need is to store Strapi's address, and the most convenient way is to store it inside the env variables; NextJS already comes with a built-in environment variable module, so there is no need to install dotenv, all we need to do is to go to next-config.js, and add the following export.

module.exports = {
  env: {
    STRAPI_ADDRESS: "http://127.0.0.1:1337/graphql"
  },
  nextConfig,
}
Enter fullscreen mode Exit fullscreen mode

And the last thing we need is an ApolloClient to be to connect with the GraphQL server, inside the graphql folder, create a new file and name it client.tsx, then add the following code.

import {ApolloClient, InMemoryCache, NormalizedCacheObject} from "@apollo/client";

const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
    uri: process.env.STRAPI_ADDRESS,
    cache: new InMemoryCache(),
});

export default client;
Enter fullscreen mode Exit fullscreen mode

Note: the cache is a way for the ApolloClient to cache results, it's a big topic and out of scope for this post. More info can be found here.

Fetching Data

Now we can start implementing the fetching procedure; let's start by creating the getStaticProps function:

export const getStaticProps: GetStaticProps<Props> = async () => {

}
Enter fullscreen mode Exit fullscreen mode

We are going to use getStaticProps because it will pre-render the page at build time, and the data will not be changing consistently; there is no need to send a request to our CMS every time.

Now we can start querying Strapi; let's get a list of blogs.

    const {data} = await client.query({query: LIST_BLOG})
    const blogs: IBlog[] = data.blogPosts.data;

    return {
        props: {
            blogs
        }
    }
Enter fullscreen mode Exit fullscreen mode

Displaying Blog List

Now that we can retrieve a list of blogs, we can start displaying them; we need new folders. To make it easier, I will show a demo below of my project structure.

common
--elements
----blog
------BlogTitle.tsx

modules
--blog
----BlogCardList.tsx
Enter fullscreen mode Exit fullscreen mode

We need two files, BlogTitle will render the title of a blog, and BlogCardList will use BlogTitle to render a list of blogs, let's start with BlogTitle.

interface Props {
    title: string
    isClickable: boolean
    blogId?: string
}

const BlogTitle: NextPage<Props> = ({title, isClickable, blogId}) => {
    if(isClickable && blogId) {
        return (
            <Link href={`/blog/${blogId}`}>{title}</Link>   
        )
    } else {
        return (
            <p>{title}</p>
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: Because I know in the commercial version, I will be using the blog title in multiple locations, and it will have more props and functionality attached to it, I chose to make the blog title a separate component; however, you can choose not to.

Now to the list of cards.

interface Props {
    blogs: IBlog[]
}

const BlogCardList: NextPage<Props> = ({blogs}) => {
    return (
        <div>
            {blogs.map((blog, i) => {
                return (
                    <div key={i}>
                        <BlogTitle 
                            title={blog.attributes.title} 
                            isClickable={true} 
                            blogId={blog.id}
                        />
                    </div>
                );
            })}
        </div>
    )
}

export default BlogCardList;
Enter fullscreen mode Exit fullscreen mode

Now to show the list, we need to add BlogCardList to about page.

const Blog: NextPage<Props> = ({blogs}) => {
    return (
        <BlogCardList blogs={blogs}/>
    )
}
Enter fullscreen mode Exit fullscreen mode

Now if we go to /blog, we should see a list of blog titles.

Displaying A Single Blog Post

Let's start creating the individual blog post page, create a new file, and call it [id].tsx inside the blog directory inside the pages directory, initialize a new basic react component.

import {NextPage} from "next";
import {IBlog} from "@/common/types/BlogInterfaces";

interface Props {
    blog: IBlog
}

const client = new ApolloClient({
    uri: process.env.STRAPI_ADDRESS,
    cache: new InMemoryCache()
});

const Blog: NextPage<Props> = ({blog}) => {
    return (
        <p>Blog Here<p/>
    )
}

export default Blog;
Enter fullscreen mode Exit fullscreen mode

We are going to utilize NextJS's getStaticPaths and getStaticProps functions, more info can be found here.

export const getStaticPaths: GetStaticPaths = async () => {
    const {data} = await client.query({query: LIST_ID })
    const ids: IBlogIdentification[] = data.blogPosts.data;

    const paths = ids.map(id => {
        return {params: {...id}}
    })

    return {
        paths,
        fallback: true
    }
}

export const getStaticProps: GetStaticProps<Props> = async ({params}) => {
    console.log()
    const {data} = await client.query({query: SINGLE_BLOG, variables: {blogId: params!.id}})
    const blog: IBlog = data.blogPosts.data[0];

    return {
        props: {
            blog
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And now, we can access the blog data inside the component.

const Blog: NextPage<Props> = ({blog}) => {
    console.log(blog)
    return (
        <div>
            <p>{blog.id}</p>
            <p>{blog.attributes.title}</p>
            <p>{blog.attributes.description}</p>
            <p>{blog.attributes.body}</p>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Now our blog is functional; we can retrieve and display a list of blogs and a single blog post.

Top comments (2)

Collapse
 
naucode profile image
Al - Naucode

Great article, you got my follow, keep writing!

Collapse
 
campbellgoe profile image
George O. E. Campbell

The type of $blogPost in the SINGLE POST query should be ID! instead of string.