Как создать приложение To-do с помощью Next.js и Strapi
Обновлено в июле 2023 г.
В этой статье мы научимся использовать Strapi, Next.js и GraphQL для создания простого приложения для создания дел.
Что такое Страпи?
Strapi — это самая передовая автономная система управления контентом Node.js с открытым исходным кодом, используемая для быстрого и эффективного создания масштабируемых, безопасных, готовых к использованию API, экономящих разработчикам бесчисленные часы работы. разработка. Благодаря расширяемой системе плагинов он предоставляет огромный набор встроенных функций: панель администратора, управление аутентификацией и разрешениями, управление контентом, генератор API и т. д. Strapi на 100 % имеет открытый исходный код, что означает :
- Strapi полностью бесплатен.
- Вы можете разместить его на своих серверах, чтобы данные принадлежали вам.
- Он полностью настраиваем и расширяем благодаря системе плагинов.
Что такое Next.js?
Next.js — это легкая платформа React для создания приложений, отображаемых на сервере. Next.js возьмет на себя тяжелую работу по сборке приложения, такую как разделение кода, HMR (горячая замена модулей) SSR (рендеринг на стороне сервера), и позволит нам сосредоточиться на написании кода, а не на написании кода. наша конфигурация сборки.
Что такое GraphQL?
GraphQL — это язык запросов, который позволяет интерфейсу приложения легко запрашивать API приложения. Каждый запрос запрашивает только те данные, которые необходимо отобразить текущим представлением. Это позволяет разработчику обеспечить удобство работы с пользователем на множестве устройств и размеров экрана.
Strapi гарантирует, что мы следуем лучшим практикам при создании и использовании веб-ресурсов через HTTP.
Например, у нас есть книжный ресурс: /books
. Глаголы HTTP обозначают действие, которое необходимо выполнить с ресурсами книги. Книги становятся коллекцией, и можно добавить новую книгу, отредактировать книгу и удалить книгу.
Следующий запрос CRUD можно выполнить к ресурсу книги, используя:
- ПОСТ
api/books
- ПОЛУЧИТЕ
api/books
иapi/books/:id
, чтобы получить конкретную книгу - ПОСТАВИТЬ
api/books/:id
- УДАЛИТЬ
api/books/:id
Книги становятся коллекцией, и можно выполнять любое из вышеперечисленных действий CRUD.
Предварительные условия
Чтобы эффективно следовать этой статье, убедитесь, что у вас есть:
- Node.js: поддерживаются только версии Maintenance и LTS (
v14
,v16
иv18
). - Узел v18.x рекомендуется для Strapi
v4.3.9
и выше. - Узел v16.x рекомендуется для Strapi от
v4.0.x
доv4.3.8
. - Менеджер пакетов, желательно Пряжа.
- Вы можете установить пряжу, используя
npm i -g yarn
, которая установит пряжу глобально. - Базовые знания NextJS.
- Редактор кода, желательно Visual Studio Code.
Теперь, когда все готово, мы можем приступить к созданию приложения to-do.
Настройка Страпи
Прежде чем мы сможем настроить Strapi, нам нужно создать папку, содержащую исходный код. Откройте терминал в нужном каталоге и запустите приведенный ниже код:
mkdir strapi-todo-blog
cd strapi-todo-blog
Затем откройте папку strapi-todo-blog
в Visual Studio Code. Теперь мы можем запустить следующие строки кода во встроенном терминале Vs Code для установки Strapi.
npx create-strapi-app@latest todo-api --quickstart
После успешной установки вы получите сообщение об успешной установке, подобное приведенному ниже:
Далее вы будете перенаправлены на страницу администратора. Здесь вы создадите своего первого администратора. Заполните запрошенную информацию и нажмите кнопку Начать.
Если вы не были перенаправлены и приложение Strapi не запущено на вашем терминале, запустите Strapi с помощью
yarn develop
и вручную перейдите к http://localhost:1337/admin/auth/register-admin
Нажатие на кнопку создаст вашу панель администратора, как показано ниже:
Создание коллекций
Теперь мы создадим веб-ресурсы. Перейдите к Content-Types Builder
и нажмите Create new collection
тип. Появится модальное окно, введите todo
в качестве Отображаемого имени и нажмите Продолжить.
Появится модальное окно, в котором мы выбираем поле для нашего типа коллекции. Нажмите на Text
.
Введите todoText
и нажмите кнопку Готово ****.
Создав поле todoText
, нажмите Сохранить в правом верхнем углу. Это приведет к перезагрузке сервера Strapi. После успешного перезапуска Strapi нажмите Диспетчер контента на боковой панели навигации, выберите тип коллекции todo и Создайте новую запись. .
Затем введите задачу в todoText, нажмите «Сохранить», а затем нажмите «Опубликовать».
Задача будет похожа на приведенную ниже и будет содержать TODOTEXT, CREATEDAT, UPDATEDAT и STATE:
Включение доступа
Strapi предназначен для обеспечения безопасности вашего приложения путем ограничения доступа к различным конечным точкам API. Выполнение запроса CRUD к типу коллекции todo
без предоставления разрешения приведет к ошибке 403
Forbidden, как показано ниже.
В Strapi есть две роли, которым можно предоставить разрешение.
- Аутентифицированные пользователи: для пользователей, вошедших в систему.
- Публичный (неавторизованные пользователи): для пользователей, не вошедших в систему.
- Из-за простоты этого приложения мы не будем разрабатывать систему входа в систему. Не стесняйтесь проверить эту статью, чтобы создать ее.
Чтобы предоставить доступ:
- Перейдите в Настройки, затем Роли и разрешения.
- Нажмите на роль
Public
. - Откройте аккордеон, который находится в разделе
Todo
. - Установите флажок
Select all
. - Нажмите Сохранить в правом верхнем углу.
Теперь, когда мы попытаемся выполнить предыдущий запрос еще раз, мы получим 200
успешное сообщение, как показано ниже:
Включение GraphQL
По умолчанию API, созданные с помощью Strapi, являются конечными точками REST. Конечные точки можно легко интегрировать в конечные точки GraphQL с помощью встроенного плагина GraphQL. Чтобы установить GraphQL в приложение, откройте папку todo-api в своем терминале и запустите строку кода ниже:
npm install @strapi/plugin-graphql
После успешной установки перезапустите сервер Strapi, перейдите по адресу http://localhost:1337/graphql и попробуйте выполнить следующий запрос:
query Todo {
todos {
data {
id
attributes {
todoText
}
}
}
}
Вы должны получить результат, аналогичный приведенному ниже.
Создание приложения Next.js
Мы создали API Strapi, и следующим шагом будет создание интерфейса.
Убедитесь, что вы находитесь в каталоге
strapi-todo-blog
.
Откройте свой терминал в каталоге strapi-todo-blog
и запустите приведенный ниже код, чтобы установить NextJS.
npx create-next-app@latest todo-app
При запуске команды create-next-app вам будет задано несколько вопросов. Следуйте инструкциям и ответьте в зависимости от ваших предпочтений или как показано ниже:
Настройка Аполло
В этом уроке мы будем использовать Apollo Client для подключения нашего приложения NextJS к конечной точке Grapi Grapi. Откройте свой терминал в папке todo-app
и запустите приведенный ниже код, который установит все зависимости, необходимые для работы Apollo:
npm install @apollo/client@alpha @apollo/experimental-nextjs-app-support --legacy-peer-deps
Теперь создайте папку в каталоге src
с именем lib
и создайте в ней файл с именем client.tsx
. После создания файла мы применим логику Apollo во вновь созданном файле.
// src/lib/client.tsx
"use client";
import { HttpLink, SuspenseCache, ApolloLink } from "@apollo/client";
import {
NextSSRApolloClient,
ApolloNextAppProvider,
NextSSRInMemoryCache,
SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr";
const STRAPI_URL = process.env.STRAPI_URL || "http://localhost:1337";
function makeClient() {
const httpLink = new HttpLink({
uri: `${STRAPI_URL}/graphql`,
});
return new NextSSRApolloClient({
cache: new NextSSRInMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
new SSRMultipartLink({
stripDefer: true,
}),
httpLink,
])
: httpLink,
});
}
function makeSuspenseCache() {
return new SuspenseCache();
}
export function ApolloWrapper({ children }: React.PropsWithChildren) {
return (
<ApolloNextAppProvider
makeClient={makeClient}
makeSuspenseCache={makeSuspenseCache}
>
{children}
</ApolloNextAppProvider>
);
}
Выше мы создали поставщика Apollo-wrapper
. Этот поставщик будет обертывать React.ReactNode
(children
), позволяя Apollo работать со всеми клиентскими компонентами приложения. Далее мы импортируем поставщика Apollo-wrapper
в файл layout.tsx
.
// src/app/layout.tsx
import "./globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ApolloWrapper } from "@/lib/client"; //Importing the ApolloWrapper Provider
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Todo app",
description: "Generated by Fredrick Emmanuel",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className} suppressHydrationWarning={true}>
<ApolloWrapper>
{/* Wrapping the React.ReactNode*/}
{children}
</ApolloWrapper>
</body>
</html>
);
}
Не стесняйтесь проверить эту статью для справки.
Настройка внешнего интерфейса
Наше приложение todo будет выглядеть так:
Мы разделим его на различные составляющие:
Разделы Header
и TodoItem
будут находиться в папке components
, а AddTodo
и TodoList
— в папке containers
. Создайте новую папку с именем components
в каталоге src
и создайте два файла: Header.tsx
и TodoItem.tsx
. Затем создайте еще одну папку в каталоге src
с именем containers
и создайте два файла: AddTodo.tsx
и Todolist.tsx
. Ваш каталог src
должен выглядеть так:
📂src
┃ ┣ 📂app
┃ ┃ ┣ 📜favicon.ico
┃ ┃ ┣ 📜globals.css
┃ ┃ ┣ 📜layout.tsx
┃ ┃ ┣ 📜page.module.css
┃ ┃ ┗ 📜page.tsx
┃ ┣ 📂components
┃ ┃ ┣ 📜Header.tsx
┃ ┃ ┗ 📜TodoItem.tsx
┃ ┣ 📂containers
┃ ┃ ┣ 📜AddTodo.tsx
┃ ┃ ┗ 📜TodoList.tsx
┃ ┗ 📂lib
┃ ┃ ┗ 📜client.tsx
Давайте конкретизируем файлы: откройте файл globals.css
и замените в нем код кодом ниже:
/* src/app/globals.css */
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-size: xx-large;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
.main {
padding: 10px 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.header {
display: flex;
justify-content: center;
color: rgba(70, 130, 236, 1);
}
.todoInputText {
padding: 10px 7px;
border-radius: 3px;
margin-right: 2px;
margin-left: 2px;
width: 100%;
font-size: large;
}
button {
padding: 10px 10px;
border-radius: 3px;
cursor: pointer;
margin-right: 2px;
margin-left: 2px;
font-size: large;
}
.bg-default {
background-color: rgba(70, 130, 236, 1);
border: 1px solid rgba(28, 28, 49, 1);
color: white;
}
.bg-danger {
background-color: red;
border: 1px solid rgba(28, 28, 49, 1);
color: white;
}
.todoInputButton {
padding: 10px 10px;
border-radius: 3px;
background-color: rgba(70, 130, 236, 1);
color: white;
border: 1px solid rgba(28, 28, 49, 1);
cursor: pointer;
margin-right: 2px;
margin-left: 2px;
font-size: large;
}
.addTodoContainer {
margin-top: 4px;
margin-bottom: 17px;
width: 500px;
display: flex;
justify-content: space-evenly;
}
.todoListContainer {
margin-top: 9px;
width: 500px;
}
.todoItem {
padding: 10px 4px;
color: rgba(70, 130, 236, 1);
border-radius: 3px;
border: 1px solid rgba(28, 28, 49, 1);
margin-top: 9px;
margin-bottom: 2px;
display: flex;
justify-content: space-between;
}
.todosText {
padding-bottom: 2px;
border-bottom: 1px solid;
}
Затем откройте файл Header.tsx
и добавьте следующее:
// src/components/Header.tsx
export default function Header() {
return (
<div className="header">
<h2>ToDo app</h2>
</div>
);
}
Теперь мы импортируем компонент Header
и разместим его над React.ReactNode
в файле layout.tsx
.
// src/app/layout.tsx
import "./globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ApolloWrapper } from "@/lib/client"; //Importing the ApolloWrapper Provider
const inter = Inter({ subsets: ["latin"] });
import Header from "@/components/Header";
//The rest of the code
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className} suppressHydrationWarning={true}>
<ApolloWrapper>
{/* Wrapping the React.ReactNode*/}
<Header />
{/* Header */}
{children}
</ApolloWrapper>
</body>
</html>
);
}
В AddTodo.tsx
добавьте:
// src/container/AddTodo.tsx
import { useState } from "react";
function AddTodo({ addTodo }: { addTodo: FunctionStringCallback }) {
const [todo, setTodo] = useState<string>("");
return (
<>
<div className="addTodoContainer">
<input
className="todoInputText"
type="text"
placeholder="Add new todo here..."
id="todoText"
value={todo}
onChange={(e) => {
setTodo(e.target.value);
}}
onKeyDown={(e) => {
if (e.code === "Enter") {
addTodo(todo);
setTodo("");
}
}}
/>
<input
className="todoInputButton"
type="button"
value="Add Todo"
onClick={() => {
addTodo(todo);
setTodo("");
}}
/>
</div>
</>
);
}
export default AddTodo;
На основе приведенного выше кода мы извлекли функцию addTodo
из props
и вызывали ее при нажатии Enter
или при нажатии кнопки Add Todo
. Эта функция отправляет значение ввода (todo
), используя передачу реквизита от дочернего элемента к родительскому. Откройте файл TodoItem.tsx
и добавьте в него следующее:
// src/coomponents/TodoItem.tsx
interface ItemTodo {
todo: any;
editTodoItem: FunctionStringCallback;
deleteTodoItem: FunctionStringCallback;
}
function TodoItem({ todo, editTodoItem, deleteTodoItem }: ItemTodo) {
return (
<>
<div className="todoItem">
<div>{todo.attributes.todoText}</div>
<div>
<i>
<button className="bg-default" onClick={() => editTodoItem(todo)}>
Edit
</button>
</i>
<i>
<button className="bg-danger" onClick={() => deleteTodoItem(todo)}>
Del
</button>
</i>
</div>
</div>
</>
);
}
export default TodoItem;
В приведенном выше коде отображается каждый элемент списка дел, состоящий из кнопок todoText
, Edit
и Del
. Затем импортируйте TodoItem
в файл TodoList.tsx
.
// src/containers/TodoList.tsx
import TodoItem from "@/components/TodoItem";
interface ListTodo {
todos: any;
editTodoItem: any;
deleteTodoItem: any;
}
function TodoList({ todos, editTodoItem, deleteTodoItem }: ListTodo) {
return (
<div className="todoListContainer">
<div className="todosText">Todos</div>
{todos
?.sort((a: any, b: any) =>
b.attributes.createdAt.localeCompare(a.attributes.createdAt)
)
.map((todo: any) => {
return (
<TodoItem
todo={todo}
key={todo.id}
deleteTodoItem={deleteTodoItem}
editTodoItem={editTodoItem}
/>
);
})}
</div>
);
}
export default TodoList;
Этот код проходит через todos
и отображает каждый TodoItem
. Наконец, мы импортируем файлы AddTodo.tsx
и TodoList.tsx
в файл pages.tsx
и передадим соответствующие реквизиты.
// src/app/page.tsx
"use client";
import { useEffect, useState } from "react";
import AddTodo from "@/containers/AddTodo";
import TodoList from "@/containers/TodoList";
export default function Home() {
const [todos, setTodos] = useState<[]>([]);
const addTodo = async (todoText: string) => {};
const editTodoItem = async (todo: any) => {
console.log("Edited");
};
const deleteTodoItem = async (todo: any) => {
console.log("Deleted");
};
return (
<div>
<main className="main">
<AddTodo addTodo={addTodo} />
<TodoList
todos={todos}
deleteTodoItem={deleteTodoItem}
editTodoItem={editTodoItem}
/>
</main>
</div>
);
}
Создание CRUD-запросов
Мы подошли к основной части этого урока. В этом разделе мы займемся
- Получение всех задач
- Создание задачи
- Редактирование задач и
- Удаление задачи из Headless CMS Strapi.
Начнем 🎉 .
Получение всех задач
Создайте в каталоге src
папку с именем query
, создайте файл с именем Schema.tsand add the following lines of code to the
schema.ts`.
// src/query/schema.ts
import { gql } from "@apollo/client";
export const GETQUERY = gql`
{
todos(sort: "id:desc") {
data {
id
attributes {
todoText
createdAt
}
}
}
}
`;
Выше мы получим все новейшие данные, используя sort: "id:desc"
.
Файл
schema.ts
будет содержать схему для всех запросов Graphql.
Затем импортируйте GETQUERY
и useQuery
в файл page.tsx
:
// src/app/page.tsx
"use client";
import { useEffect, useState } from "react";
import AddTodo from "@/containers/AddTodo";
import TodoList from "@/containers/TodoList";
import { useQuery } from "@apollo/experimental-nextjs-app-support/ssr";
import { GETQUERY } from "@/query/schema";
export default function Home() {
const [todos, setTodos] = useState<[]>([]);
const { loading, error, data } = useQuery(GETQUERY, {
fetchPolicy: "no-cache",
}); //Fetching all todos
useEffect(() => {
setTodos(data?.todos?.data); //Storing all the todos
}, [data]);
//rest of the code
}
Здесь мы получили все задачи из Strapi с помощью useQuery
и сохранили их в состоянии todos
. Перейдите по адресу http://localhost:3000, и вы должны получить результат, аналогичный приведенному ниже:
Strapi по умолчанию устанавливает максимальное количество извлекаемых данных равным 10. Чтобы изменить это, просмотрите оригинальную документацию Strapi.
Создание задачи
Прежде чем мы сможем отправить запрос в Strapi на добавление задачи, нам нужно сделать следующее:
- Нажмите
Content-Type Builder
на боковой панели навигации и кнопку Изменить. - Нажмите ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ, снимите флажок Черновик и публикация и нажмите Готово. › Strapi имеет настройку по умолчанию, которая позволяет администраторам просматривать каждый отправленный контент, предоставляя им возможность просматривать и оценивать его.
- Затем нажмите значок «Изменить», чтобы отредактировать todoText.
- Нажмите ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ, отметьте Обязательное поле, нажмите кнопку Готово и нажмите кнопку Сохранить в правом верхнем углу. .
Как только это будет сделано, мы сможем создать задачу. Откройте файл schema.ts
и добавьте схему для добавления задачи.
// src/query/schema.ts
import { gql } from "@apollo/client";
//The rest of the code
export const ADDMUT = gql`
mutation createTodo($todoText: String) {
createTodo(data: { todoText: $todoText }) {
data {
id
attributes {
todoText
createdAt
}
}
}
}
`;
Чтобы добавить задачу, мы импортируем схему ADDQUERY
из schema.ts
и функцию useMutation
из apollo/client
.
// src/app/page.tsx
//The rest of the code
import { useMutation } from "@apollo/client";
import { GETQUERY, ADDMUT } from "@/query/schema";
export default function Home() {
const [todos, setTodos] = useState<[]>([]);
const [createTodo] = useMutation(ADDMUT);
const { loading, error, data } = useQuery(GETQUERY, {
fetchPolicy: "no-cache",
}); //Fetching all todos
useEffect(() => {
console.log(data?.todos?.data);
setTodos(data?.todos?.data); //Storing all the todos
}, [data]);
const addTodo = async (todoText: string) => {
await createTodo({
//Creating a new todo
variables: {
todoText: todoText, //Passing the todo text
},
}).then(({ data }: any) => {
setTodos([...todos, data?.createTodo?.data] as any); //Adding the new todo to the list
});
};
//The rest of the code.
}
Редактирование задачи
Чтобы обновить данные в Strapi с помощью Graphql, мы будем использовать id конкретной задачи, которую мы обновляем. Откройте файл schema.ts и добавьте мутацию Graphql для функции обновления.
// src/query/schema.ts
import { gql } from "@apollo/client";
//The rest of the code
export const UPDATEMUT = gql`
mutation updateTodo($id: ID!, $todoText: String!) {
updateTodo(id: $id, data: { todoText: $todoText }) {
data {
id
attributes {
todoText
createdAt
}
}
}
}
`;
Теперь импортируйте схему UPDATEMUT в файл pages.tsx.
// src/app/page.tsx
//The rest of the code
import { GETQUERY, ADDMUT, UPDATEMUT } from "@/query/schema";
export default function Home() {
const [todos, setTodos] = useState<[]>([]);
const [createTodo] = useMutation(ADDMUT);
const [updateTodo] = useMutation(UPDATEMUT);
//The rest of the code
const editTodoItem = async (todo: any) => {
const newTodoText = prompt("Enter new todo text or description:");
if (newTodoText != null) {
await updateTodo({
//updating the todo
variables: {
id: todo.id,
todoText: newTodoText,
},
}).then(({ data }: any) => {
const moddedTodos: any = todos.map((_todo: any) => {
if (_todo.id === todo.id) {
return data?.updateTodo?.data;
} else {
return _todo;
}
});
setTodos(moddedTodos);
});
}
};
//The rest of the code
}
Функция editTodoItem
редактирует задачу. Он предлагает пользователю ввести новый текст задачи. После нажатия кнопки «ОК» в диалоговом окне подсказки задача редактируется с помощью задачи id
.
Удаление задачи
Мы подошли к последнему разделу нашего CRUD-запроса. В этом разделе мы удалим задачу, используя идентификатор задачи. Откройте файл schema.ts и добавьте DELETEMUT
.
// src/query/schema.ts
import { gql } from "@apollo/client";
//The rest of the code
export const DELETEMUT = gql`
mutation deleteTodo($id: ID!) {
deleteTodo(id: $id) {
data {
id
attributes {
todoText
createdAt
}
}
}
}
`;
Импортируйте схему DELETEMUT
в pages.tsx.
// src/app/page.tsx
//The rest of the code
import { GETQUERY, ADDMUT, UPDATEMUT, DELETEMUT } from "@/query/schema";
export default function Home() {
const [todos, setTodos] = useState<[]>([]);
const [createTodo] = useMutation(ADDMUT);
const [updateTodo] = useMutation(UPDATEMUT);
const [deleteMUT] = useMutation(DELETEMUT);
//The rest of the code
const deleteTodoItem = async (todo: any) => {
if (confirm("Do you really want to delete this item?")) {
await deleteMUT({
//Deleting the todo
variables: {
id: todo.id,
},
}).then(({ data }: any) => {
const newTodos = todos.filter((_todo: any) => _todo.id !== todo.id);
setTodos(newTodos as any);
});
}
};
//The rest of the code
}
Получите полный исходный код из раздела ресурсов.
Функция deleteTodoItem
принимает объект задачи для удаления в аргументе todo
. Сначала он подтверждает, хочет ли пользователь удалить задачу, и если да, он приступает к удалению задачи. Это приведет к тому, что Strapi удалит задачу с идентификатором в своей базе данных. Затем мы отфильтровываем удаленные задачи и устанавливаем отфильтрованный результат в качестве новых задач в состоянии задач.
Ресурсы
Исходный код приложения вы можете найти здесь:
Вы также можете прочитать больше о Strapi иgraphql, используя ссылки:
Заключение
Ура 🎉, мы подошли к концу этой статьи. В статье показано, как интегрировать Strapi в NextJS, создав приложение для создания дел. Не стесняйтесь проверять приложение на разных компьютерах и комментировать любые ошибки, если они возникнут.