Typescript,NextJS,Strapi和GraphQl:我如何创建博客的概述
#javascript #网络开发人员 #react #softwaredevelopment

首先,让我们介绍要使用的堆栈。 Typescript是“是一种基于JavaScript的强烈键入编程语言,可在任何规模上为您提供更好的工具。”简单地说,打字稿增强了编码安全性并为网络启用更多OOP。

nextjs是一个框架,“使您可以通过扩展最新的React功能来创建全堆栈Web应用程序”,简单单词,NextJS是对广泛使用的ReactJS库的升级。

Strapi是一个开源无头CM。 CMS代表“内容管理系统”,允许我们在没有编程知识的情况下在用户友好的界面中创建,管理,修改和发布内容。无头意味着没有内置的最终用户UI;我们可以使用我们想要的任何前端技术。

GraphQl是“ API的查询语言,也是用您现有数据来满足这些查询的运行时”,用简单的单词,这是一种用于查询数据并获取所需字段的语言。

注意:这是一个概述,这意味着我不会浏览一些方面,例如样式和最佳HTML标签;该博客旨在展示使用上述技术创建博客的功能。

初始化项目

现在,让我们使用命令行创建文件夹(因为它很酷),因此第一个命令是创建一个包含我们所有文件的文件夹。

mkdir my-personal-website

现在,让我们为NextJ和Strapi创建文件夹。

cd my-personal-website
mkdir frontend, strapi

初始化strapi

cd strapi
npx create-strapi-app@latest . --ts --quickstart

参数@latest表示它将安装最新版本,.表示我们要在当前文件夹中创建项目,然后— ts表示它将使用Typescript,最后将其安装为默认设置;安装完成后,它将在地址/admin/auth/register-admin上打开一个新窗口,要求您创建一个新的管理员帐户;继续创建一个新帐户。注意:使用— ts是可选的,因为我不会更改本文中的源代码。

初始化nextjs

cd frontend
npx create-next-app@latest . --ts

最后,请记住要发动github存储库,但我不会在这篇文章中介绍它。

从Strapi开始

我喜欢从项目中的后端开始,所以我将从strapi开始,因为它被认为是后端。

安装GraphQL

使用Strapi的一个优点是它与GraphQl的简单集成。要安装GraphQL插件,我们需要运行以下命令。

npm run strapi install graphql

如果您遇到_Duplicate "graphql" modules错误,请不要担心;很容易修复;您需要做的就是删除 node_modules 文件夹和 package-lock.json ,然后我们需要重新安装软件包。

rm node_modules
rm package-lock.json
npm install
npm run strapi dev

成功安装后,我们现在可以访问url /graphql

创建博客文章集合

在url http://localhost:1337/admin/上打开strapi主页,然后登录后,转到 content-type builder 页面,单击 创建新的新收集类型 按钮,然后在 中显示名称 字段,用适当的名称填写;我将选择 博客文章 ,然后单击继续。

现在,我们正在为新系列选择领域。您可以根据需要自定义它;为了简单起见,我将添加三个字段,a 文本 标题的字段,另一个 text 字段对于描述,最后是 丰富的文本 字段,下一步单击保存。

现在我们有了一个集合,我们需要用数据填充它,转到 content Manager ,现在我们可以看到新创建的收藏集,单击它,然后 创建新条目 按钮,然后用所需的任何数据填充它,然后单击“保存”并发布。

可访问性选项1:可公开访问

在我们可以通过GraphQl访问数据之前,我们需要使其可访问;您可以使其公开访问或仅适用于经过身份验证的用户。由于我没有存储敏感数据,因此我会选择一个公开访问的集合。

要公开访问它,转到 设置, 然后角色,单击 public ,向下滚动到您创建的集合(在我的情况下是博客帖),单击它,然后tick find findOne。

可访问性选项2:仅身份验证

但是,如果您选择身份验证的方式,这就是方法。有两种方法可以实现这一目标;第一个是在设置页面中创建 api令牌 ,然后我们需要通过将其添加到标题中,以 将其添加到标题中。授权 标题并将值设置为 bearer ,第二种方法是在 用户中创建一个新条目 收集,通过 内容管理器 页面,然后发送 post 请求向/api/auth/local带有包含标识符的JSON主体,该标识符可以是 user 收集中新创建的用户的用户名或电子邮件和密码,不是管理员帐户我们在初始化strapi中创建了。

{
 "identifier": "email or username",
 "password": "password"
}

如果请求成功,您将获得包含 jwt 字段的响应,现在我们需要做同样的事情,请通过标头通过标头发送每个请求在 授权中

现在访问它,转到 设置, 然后角色,单击 authenticated ,向下滚动到您创建的集合(在我的情况下是博客帖),单击它,然后tick find findOne。

测试GraphQL端点

要访问GraphQl到/graphql,现在,我们可以在左侧看到输入字段;在这里,我们将查询查询我们的strapi应用程序,这是查询的一个示例:

query {
  blogPosts {
    data {
      id
      attributes {
        title
        description
        body
      }
    }
  }
}

,就我而言,响应是:

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

测试休息端点

我们需要做的就是向/api/blog-posts/发送GET请求。响应的一个例子是:

{
    "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
        }
    }
}

我们已经完成了Strapi部分,并将继续进行NextJS。

从nextjs开始

页面内创建一个新目录 目录,并将其命名 blog ;接下来,创建一个新的index.tsx文件并初始化基本的React组件。

import {NextPage} from "next";

interface Props {
}

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

export default About;

现在我们要访问blog并查看大约页面。

创建接口

我们将需要一些接口;让我们首先创建一个包含我们所有基本模块的文件夹 common ,然后创建另一个称为 type 的文件夹包含我们所有类型和接口,然后创建一个新的打字稿,我将命名 bloginterfaces ;现在,让第一个接口(将要访问IBlogIdentification),它将包含我们的标识字段;在我们的情况下,这是博客文章的ID。

export interface IBlogIdentification {
    id: string
}

接下来,我们需要一个接口来保存博客字段的标题,描述和身体;命名IBlogFields

export interface IBlogFields {
    title: string,
    description: string,
    body: string
}

接下来,我们需要一个从GraphQl查询中收到的博客属性的接口;命名IBlogAttributes

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

我已经使用了部分和选择实用程序类型来使所有IBlogFields字段可选。

最后,我们将需要一个接口来处理整个博客条目;命名IBlog

export interface IBlog extends IBlogIdentification, IBlogAttributes{}

现在让我们在 博客 page上更新道具接口。

interface Props {
    blogs: IBlog[]
}

GraphQl有效

在我们开始获取数据之前,我们需要进行GraphQl工作。首先,我们需要安装两个软件包才能与GraphQl进行交互。我们将需要graphql@apollo/client packages

npm i graphql, @apollo/client

第二,我们需要编写查询;我们将需要三个查询,第一个列出博客,第二个是获取一个博客,第三个是获取博客ID列表”;在 common 文件夹中,创建一个新文件夹并将其称为graphql,然后创建一个新文件并调用queries.tsx,然后添加以下查询。

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
    }
  }
}`)

我们需要的第三件事是存储Strapi的地址,最方便的方法是将其存储在 env 变量中; NextJS已经带有内置环境变量模块,因此无需安装dotenv,我们需要做的就是转到next-config.js,并添加以下导出。

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

,我们需要的最后一件事是一个ApolloClient是要与graphql server连接,在 graphql 文件夹中,创建一个新文件并将其命名为client.tsx,然后添加以下代码。

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

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

export default client;

注意:缓存是ApolloClient缓存结果的一种方式,这是一个很大的话题,并且不在此帖子的范围内。可以找到更多信息here

获取数据

现在我们可以开始实施获取过程;让我们从创建getStaticProps函数开始:

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

}

我们将使用getStaticProps,因为它将在构建时间预渲染页面,并且数据不会始终如一地更改;无需每次向我们的CMS发送请求。

现在我们可以开始查询strapi;让我们获取博客列表。

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

    return {
        props: {
            blogs
        }
    }

显示博客列表

现在我们可以检索博客列表,我们可以开始显示它们;我们需要新的文件夹。为了使它更轻松,我将在我的项目结构下方展示一个演示。

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

modules
--blog
----BlogCardList.tsx

我们需要两个文件,BlogTitle将呈现博客的标题,而BlogCardList将使用BlogTitle渲染博客列表,让我们从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>
        )
    }
}

注意:因为我在商业版中知道,我将在多个位置使用博客标题,并且它将具有更多的道具和功能,所以我选择使博客标题成为单独的组件;但是,您可以选择不。

现在到纸牌列表。

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;

现在要显示列表,我们需要将BlogCardList添加到大约页面。

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

现在,如果我们转到/blog,我们应该看到博客标题的列表。

显示一篇博客文章

让我们开始创建单个博客文章页面,创建一个新文件,然后在 博客内将其称为[id].tsx 目录 页面 目录,初始化一个新的基本反应组件。

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;

我们将使用NextJS的getStaticPathsgetStaticProps功能,可以找到更多信息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
        }
    }
}

现在,我们可以访问组件内的博客数据。

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>
    )
}

现在我们的博客功能正常;我们可以检索并显示博客列表和单个博客文章。