发布于 2026-01-06 3 阅读
0

使用 Next.js 和 AppWrite 构建留言板

使用 Next.js 和 AppWrite 构建留言板

太长不看

本文将介绍如何使用 Next.js 和 AppWrite 构建一个简单的留言板 Web 应用。这是一个很好的例子,可以帮助我们更好地学习如何使用这些工具,并在过程中构建一些有用的应用。

一个小小的请求

我正在努力让Preevy获得 1000 个 GitHub 星标。Preevy 是我和我的团队刚刚推出的一个开源工具,旨在轻松创建可共享的预览环境。
您能否帮忙给 Preevy 的 GitHub 仓库点个星标呢?这将对我们非常有帮助!谢谢!https://github.com/livecycle/preevy

现在,如果你准备好了,我们就开始吧。

潜水

介绍

本文将介绍如何使用Next.jsAppWrite构建一个简单的留言板 Web 应用。最终,我们将拥有一个可运行的 Web 应用,用户可以在其中进行身份验证、发布消息并阅读其他用户的消息。我与团队中的开发人员共同编写了本指南。我们认为,这是一个很好的学习案例,可以帮助大家更好地使用这些工具,并在过程中构建一些实用的应用。

Next.js 是一个流行的 React 框架,它提供卓越的性能和强大的服务器端渲染功能,是创建交互式 Web 应用的首选。而 AppWrite 则让编写后端代码变得极其简单,无需自行搭建后端。您可以直接从 Next.js 前端调用它。AppWrite 可以为您处理传统后端的所有任务:数据库、身份验证、文件上传等等。

样式方面,我们将使用TailwindCSS。为了处理前端对 AppWrite 的调用,我们将使用数据获取和缓存库 React Query。

设置

系统先决条件

AppWrite 安装

在开发过程中,我们会在本地机器上部署一个 AppWrite 实例。这可以通过 Docker 实现。您的机器上应该已经配置好了 Docker。AppWrite 内部包含多种服务,例如 Redis 和 MariaDB。手动配置所有这些服务会很麻烦。幸运的是,AppWrite 提供了一个docker-compose配置工具,只需一条命令即可启动所有服务。

首先,创建一个新文件夹message-board/,并在该文件夹中运行此命令来配置并启动 AppWrite 服务。下载 Docker 镜像可能需要一些时间。在设置过程中,所有选项都选择默认设置,并输入一个随机生成的密钥。

docker run -it --rm \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="install" \
    appwrite/appwrite:1.3.4
Enter fullscreen mode Exit fullscreen mode

之后,访问http://localhost并创建一个新的管理员帐户来管理 AppWrite。这样,我们就可以创建和配置 AppWrite 项目了。首先,注册一个新帐户。然后,点击“创建项目”并输入message-board项目名称。

1

现在选择“添加平台”→“Web应用程序”。message-board再次命名,并将其用作*主机名,因为这仅用于本地开发,我们希望确保可以从Next.js应用程序访问AppWrite。

6

您可以跳过有关 JavaScript 设置的可选步骤,我们稍后会进行设置。进入控制面板后,转到“数据库”并创建一个新的数据库。同样,给它命名message-board。将“数据库 ID”留空,以便系统随机生成。

3

现在在新建的数据库中创建一个集合。为此,请点击“创建集合”并将其命名为“集合” messages。同样,请将 ID 字段留空。

4

在模式中messages,创建一个新的“String”属性,长度设置为[此处应填写具体数值] 1024,并将其标记为“必填”。稍后我们将在此属性中存储用户提交的消息。

5

既然已经在这里了,我们也应该设置访问此集合的权限。请转到集合的“设置”选项卡,并根据此设置配置权限。我们将在教程的身份验证部分再次调整这些权限。

6

Next.js 设置

现在回到终端。在之前创建的文件夹中message-board/运行以下命令来配置一个新的 Next 应用。请注意我们下面选择的设置。您必须选择“启用 Tailwind CSS”和“禁用 App Router”。

7

现在切换到该message-board/message-board-app/文件夹​​,并安装 AppWrite 和 ReactQuery 所需的依赖项:

npm install appwrite react-query
Enter fullscreen mode Exit fullscreen mode

让我们用你选择的编辑器打开 Next.js 应用。在src/pages/_app.js文件中配置 React Query。稍后我们将使用它从 AppWrite 获取数据。

import { QueryClient, QueryClientProvider } from 'react-query';

import '@/styles/globals.css'

export default function App({ Component, pageProps }) {
  const queryClient = new QueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      <Component {...pageProps} />
    </QueryClientProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

现在,在你的 Next.js 文件夹中,创建一个名为 . 的新文件.env.local。我们需要在其中存储我们的 AppWrite 访问信息:

NEXT_PUBLIC_ENDPOINT=http://localhost/v1
NEXT_PUBLIC_PROJECT=646XXX
NEXT_PUBLIC_DATABASE=646XXX
NEXT_PUBLIC_MESSAGES_COLLECTION=646XXX
Enter fullscreen mode Exit fullscreen mode

只要您按照 docker-compose 的安装步骤操作,端点始终保持不变。其他三个 ID 需要从 AppWrite 控制面板复制。您可以在项目、数据库和集合名称的标题旁边找到它们。请注意不要混淆项目、数据库和集合的 ID,因为它们看起来非常相似。

8

搭建留言板

基本脚手架

首先移除页面上src/pages/index.js现有的样板代码,然后创建用于显示、创建和删除消息的框架。目前,我们将显示静态数据,稍后再添加交互功能。

import { useState } from 'react';

const messages = [
  { $id: 1, message: 'Hello world' },
  { $id: 2, message: 'Hello world 2' },
];

export default function Home() {
  const [input, setInput] = useState('');

  return (
    <main className="flex min-h-screen justify-center p-24 text-black">
      <div className="bg-white rounded-lg shadow-lg p-8">
        <h1 className="text-4xl py-8 font-bold text-center">Message board</h1>
        <ul className="space-y-4">
          {messages?.map((message) => (
            <li key={message.$id} className="flex items-center justify-between w-full space-x-4">
              <p className="text-gray-700">{message.message}</p>
              <button className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md">Delete</button>
            </li>
          ))}
        </ul>
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          className="w-full border border-gray-300 rounded-md p-2 mt-4"
        />

        <button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4">Submit message</button>
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

顺便说一下,如果你想看看应用程序的样子,可以npm run dev在终端中输入message-board-app/命令启动它。

添加新消息

首先,创建一个新文件,用于src/appwrite.js存放我们与 AppWrite 后端的所有通信。这addMessage是一个异步函数,它接受消息字符串作为输入。然后,它会调用我们的 AppWrite 实例,将消息保存到数据库中。

import { Account, Client, Databases, ID } from 'appwrite';

const client = new Client();
const account = new Account(client);

const database = process.env.NEXT_PUBLIC_DATABASE;
const collection = process.env.NEXT_PUBLIC_MESSAGES_COLLECTION;

client.setEndpoint(process.env.NEXT_PUBLIC_ENDPOINT).setProject(process.env.NEXT_PUBLIC_PROJECT);

const databases = new Databases(client);

export const addMessage = async (message) => {
  await databases.createDocument(
    database,
    collection,
    ID.unique(),
    {
      message,
    }
  );
};
Enter fullscreen mode Exit fullscreen mode

然后修改src/pages/index.js组件,使其能够连接用于添加消息的表单:

import { useState } from 'react';
import { useMutation, useQueryClient } from 'react-query';

import { addMessage } from '@/appwrite';

const messages = [
  { $id: 1, message: 'Hello world' },
  { $id: 2, message: 'Hello world 2' },
];

export default function Home() {
  const [input, setInput] = useState('');

  const queryClient = useQueryClient();

  const addMessageMutation = useMutation(addMessage, {
    onSuccess: () => {
      setInput('');
      queryClient.invalidateQueries('messages');
    },
  });

  return (
    <main className="flex min-h-screen justify-center p-24 text-black">
      <div className="bg-white rounded-lg shadow-lg p-8">
        <h1 className="text-4xl py-8 font-bold text-center">Message board</h1>
        <ul className="space-y-4">
          {messages?.map((message) => (
            <li key={message.$id} className="flex items-center justify-between w-full space-x-4">
              <p className="text-gray-700">{message.message}</p>
              <button className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md">Delete</button>
            </li>
          ))}
        </ul>
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          className="w-full border border-gray-300 rounded-md p-2 mt-4"
        />
        <button
          onClick={() => addMessageMutation.mutate(input)}
          className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4"
        >
          Submit message
        </button>
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

现在我们可以向数据库添加消息了。但是,由于我们还没有从 AppWrite 获取任何消息,所以目前还看不到这些消息。我们会尽快解决这个问题。

但我们在这里究竟在做什么呢?我们创建了一个带有useMutationHook 的 React Query mutation。它允许我们调用addMessageMutation.mutate(input)“提交消息”按钮。更重要的是,我们在 Hook 中添加了一个onSucess回调函数。因此,每当成功添加新消息时,我们都可以清除输入字段并使查询缓存失效。使缓存失效会触发 React Query 重新获取所有消息。这在下一节中将从 AppWrite 获取消息时会非常有用。

渲染消息

现在我们可以向数据库添加消息了,接下来我们还要显示这些消息。首先,我们需要将获取消息的操作添加到src/appwrite.js文件中:

// other code

export const getMessages = async () => {
  const { documents: messages } = await databases.listDocuments(database, collection);

  return messages;
};
Enter fullscreen mode Exit fullscreen mode

现在我们可以使用 React Query useQueryhook 来获取它们:

import { useState } from 'react';
import { useMutation, useQuery, useQueryClient } from 'react-query';

import { addMessage, getMessages } from '@/appwrite';

export default function Home() {
  const [input, setInput] = useState('');

  const queryClient = useQueryClient();

  const { data: messages } = useQuery('messages', getMessages);

  const addMessageMutation = useMutation(addMessage, {
    onSuccess: () => {
      setInput('');
      queryClient.invalidateQueries('messages');
    },
  });

  return (
    <main className="flex min-h-screen justify-center p-24 text-black">
      <div className="bg-white rounded-lg shadow-lg p-8">
        <h1 className="text-4xl py-8 font-bold text-center">Message board</h1>
        <ul className="space-y-4">
          {messages?.map((message) => (
            <li key={message.$id} className="flex items-center justify-between w-full space-x-4">
              <p className="text-gray-700">{message.message}</p>
              <button
                className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md"
              >
                Delete
              </button>
            </li>
          ))}
        </ul>
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          className="w-full border border-gray-300 rounded-md p-2 mt-4"
        />
        <button
          onClick={() => addMessageMutation.mutate(input)}
          className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4"
        >
          Submit message
        </button>
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

通过表单添加一些消息,这样你就能看到它们了!请注意,每当你提交一条新消息时,现有消息都会自动重新加载,以便新消息出现在列表中。这就是 React Query 的神奇之处。

删除消息

最后一步是添加删除消息的功能。同样,我们首先需要将此功能添加到src/appwrite.js

// other code

export const deleteMessage = async (id) => {
  await databases.deleteDocument(database, collection, id);
};
Enter fullscreen mode Exit fullscreen mode

接下来,只需onClick在页面上添加一个 mutation 和删除按钮的操作即可src/pages/index.js。不要复制整个组件,只需复制添加的删除 mutation 和新的 return 语句。另外,请确保deleteMessage从 AppWrite 导入。

import { useState } from 'react';
import { useMutation, useQuery, useQueryClient } from 'react-query';

import { addMessage, deleteMessage, getMessages } from '@/appwrite';

export default function Home() {
  // other code

  const deleteMessageMutation = useMutation(deleteMessage, {
    onSuccess: () => {
      queryClient.invalidateQueries('messages');
    },
  });

  return (
    <main className="flex min-h-screen justify-center p-24 text-black">
      <div className="bg-white rounded-lg shadow-lg p-8">
        <h1 className="text-4xl py-8 font-bold text-center">Message board</h1>
        <ul className="space-y-4">
          {messages?.map((message) => (
            <li key={message.$id} className="flex items-center justify-between w-full space-x-4">
              <p className="text-gray-700">{message.message}</p>
              <button
                onClick={() => deleteMessageMutation.mutate(message.$id)}
                className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md"
              >
                Delete
              </button>
            </li>
          ))}
        </ul>
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          className="w-full border border-gray-300 rounded-md p-2 mt-4"
        />
        <button
          onClick={() => addMessageMutation.mutate(input)}
          className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4"
        >
          Submit message
        </button>
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

再次,在成功删除一条消息后,我们告诉 React Query 重新获取所有消息,以便已删除的消息从列表中消失。

验证

为了添加身份验证功能,我们首先需要在src/appwrite.js文件中添加一些额外的操作。这样用户就可以注册并登录。此外,我们还需要一个注销操作和一个用于获取用户会话的函数。为了存储用户会话,还需要在该文件中创建一个新的 React Context。请确保createContextreact. 导入。

// other code

export const signUp = async (email, password) => {
  return await account.create(ID.unique(), email, password);
};

export const signIn = async (email, password) => {
  return await account.createEmailSession(email, password);
};

export const signOut = async () => {
  return await account.deleteSessions();
};

export const getUser = async () => {
  try {
    return await account.get();
  } catch {
    return undefined;
  }
};


// import createContext from react beforehand
export const UserContext = createContext(null);
Enter fullscreen mode Exit fullscreen mode

现在我们需要在每次应用程序启动时自动获取用户,并通过上下文使其可用。我们将在以下代码中实现src/pages/_app.js

import { useEffect, useState } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';

import { UserContext, getUser } from '@/appwrite';

import '@/styles/globals.css';

export default function App({ Component, pageProps }) {
  const [user, setUser] = useState(null);

  const queryClient = new QueryClient();

  useEffect(() => {
    const user = async () => {
      const user = await getUser();

      if (!user) return;

      setUser(user);
    };

    user();
  }, []);

  return (
    <UserContext.Provider value={{ user, setUser }}>
      <QueryClientProvider client={queryClient}>
        <Component {...pageProps} />
      </QueryClientProvider>
    </UserContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

接下来我们可以创建注册用的掩码。为此,请添加一个新文件,src/pages/signup.js内容如下:

import { useRouter } from 'next/router';
import { useState } from 'react';

import { signUp } from '@/appwrite';

export default function SignUp() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      await signUp(email, password);

      router.push('/');
    } catch {
      console.log('Error signing up');
    }
  };

  return (
    <div className="flex items-center justify-center h-screen">
      <div className="max-w-sm mx-auto">
        <form onSubmit={handleSubmit} className="bg-white shadow-md rounded px-8 py-6 mb-4">
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
              Email
            </label>
            <input
              className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              id="email"
              type="email"
              placeholder="Enter your email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
            />
          </div>

          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
              Password
            </label>
            <input
              className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              id="password"
              type="password"
              placeholder="Enter your password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
          </div>

          {/* Submit button */}
          <div className="flex items-center justify-between">
            <button
              className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
              type="submit"
            >
              Sign Up
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

请登录src/pages/signin.js

import { useRouter } from 'next/router';
import { useState } from 'react';

import { signIn } from '@/appwrite';

export default function SignIn() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      await signIn(email, password);

      router.push('/');
    } catch {
      console.log('Error signing in');
    }
  };

  return (
    <div className="flex items-center justify-center h-screen">
      <div className="max-w-sm mx-auto">
        <form onSubmit={handleSubmit} className="bg-white shadow-md rounded px-8 py-6 mb-4">
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
              Email
            </label>
            <input
              className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              id="email"
              type="email"
              placeholder="Enter your email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
            />
          </div>

          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
              Password
            </label>
            <input
              className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              id="password"
              type="password"
              placeholder="Enter your password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
          </div>

          <div className="flex items-center justify-between">
            <button
              className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
              type="submit"
            >
              Sign In
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

退出登录

现在,用户身份验证的最后一步是连接注销功能。我们已经有了所有需要的组件。我们只需要检查会话是否user存在,如果存在,则显示一个“注销”按钮,该按钮会调用signOutAppWrite 中的函数。我们将修改src/pages/index.js文件如下:

import { useContext, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from 'react-query';

import { addMessage, getMessages, deleteMessage, UserContext, signOut } from '@/appwrite';

export default function Home() {
  const [input, setInput] = useState('');

  const queryClient = useQueryClient();
  const { user, setUser } = useContext(UserContext);

  const { data: messages } = useQuery('messages', getMessages);

  const addMessageMutation = useMutation(addMessage, {
    onSuccess: () => {
      setInput('');
      queryClient.invalidateQueries('messages');
    },
  });

  const deleteMessageMutation = useMutation(deleteMessage, {
    onSuccess: () => {
      queryClient.invalidateQueries('messages');
    },
  });

  const handleSignOut = async () => {
    await signOut();

    setUser(null);
  };

  return (
    <main className="flex min-h-screen justify-center p-24 text-black">
      <div className="bg-white rounded-lg shadow-lg p-8">
        <h1 className="text-4xl py-8 font-bold text-center">Message board</h1>
        <ul className="space-y-4">
          {messages?.map((message) => (
            <li key={message.$id} className="flex items-center justify-between w-full space-x-4">
              <p className="text-gray-700">{message.message}</p>
              <button
                onClick={() => deleteMessageMutation.mutate(message.$id)}
                className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md"
              >
                Delete
              </button>
            </li>
          ))}
        </ul>
        <textarea
          value={input}
          onChange={(e) => setInput(e.target.value)}
          className="w-full border border-gray-300 rounded-md p-2 mt-4"
        />
        <button
          onClick={() => addMessageMutation.mutate(input)}
          className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4"
        >
          Submit message
        </button>
        {user && (
          <button
            onClick={handleSignOut}
            className="bg-red-500 ml-4 hover:bg-red-600 text-white px-4 py-2 rounded-md mt-4"
          >
            Sign out
          </button>
        )}
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

访问控制

身份验证实现后,我们现在要确保只有经过身份验证的用户才能创建新消息,并且用户只能删除自己的消息。所以,让我们回到控制面板,将messages架构权限更改为以下内容:

9

如您所见,从现在起,未经身份验证的用户只能阅读消息。如果您想创建消息,则需要先进行身份验证。但是,我们如何才能让用户删除自己的消息呢?我们将逐个文档地进行设置。您可以将文档想象成数据库中的一行数据。因此,在同一屏幕上,请确保您已启用“文档安全”功能。

10

回到代码中,addMessage按如下方式修改函数src/appwrite.js。另外,别忘了按所示更新导入语句。

import { Account, Client, Databases, Permission, Role, ID } from 'appwrite';

export const addMessage = async ({ message, userId }) => {
  await databases.createDocument(
    database,
    collection,
    ID.unique(),
    {
      message,
    },
    [Permission.delete(Role.user(userId))]
  );
};
Enter fullscreen mode Exit fullscreen mode

这样,我们就明确声明只有文档所有者才能删除它。现在,在src/app/index.js添加消息按钮的操作中,还要传递user.$id当前用户的 ID,以便 AppWrite 知道我们要授予哪个用户删除权限:

<button
  onClick={() => addMessageMutation.mutate({ message: input, userId: user?.$id })}
  className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4"
>
  Submit message
</button>
Enter fullscreen mode Exit fullscreen mode

现在,在[平台名称]创建一个新用户/signup,然后使用相同的凭据/signin登录。之后,您就可以创建与您的帐户关联的新消息了。尝试删除这些消息,看看会发生什么。

在用户界面中显示身份验证状态

太好了!现在只有消息所有者才能删除消息。但是,我们仍然在每条消息旁边显示删除按钮,无论用户登录的是哪个账号。提交新消息的字段也是如此,未登录用户也能看到它。因此,我们需要修改用户界面,使其仅在用户实际可以执行这些操作时才显示这些选项。对于提交新消息的表单,这很简单。我们只需在用户登录时显示它即可:

{user && (
  <>
    <textarea
      value={input}
      onChange={(e) => setInput(e.target.value)}
      className="w-full border border-gray-300 rounded-md p-2 mt-4"
    />
    <button
      onClick={() => addMessageMutation.mutate({ message: input, userId: user?.$id })}
      className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4"
    >
      Submit message
    </button>
    <button
      onClick={handleSignOut}
      className="bg-red-500 ml-4 hover:bg-red-600 text-white px-4 py-2 rounded-md mt-4"
    >
      Sign out
    </button>
  </>
)}
Enter fullscreen mode Exit fullscreen mode

检查用户是否拥有该消息稍微复杂一些。每条消息都带有一个权限数组。如果用户拥有该消息,则其权限信息user.$id将出现在delete权限字段中。我们将编写一个函数来检查这一点:

const canDelete = (userID, array) => {
  return array.some((element) => element.includes('delete') && element.includes(userID));
};
Enter fullscreen mode Exit fullscreen mode

然后检查每条消息的用户界面,看看是否需要渲染删除按钮:

{messages?.map((message) => (
  <li key={message.$id} className="flex items-center justify-between w-full space-x-4">
    <p className="text-gray-700">{message.message}</p>
    {canDelete(user?.$id, message.$permissions) && (
      <button
        onClick={() => deleteMessageMutation.mutate(message.$id, user.$id)}
        className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md"
      >
        Delete
      </button>
    )}
  </li>
))}
Enter fullscreen mode Exit fullscreen mode

总结

至此,我们就完成了!用户现在只会看到他们拥有相应权限的操作。正如本教程所示,Next.js、AppWrite 和 React Query 配合得非常出色。得益于 AppWrite 与 React Query 的结合,我们无需编写任何后端代码即可获得卓越的全栈开发体验。

希望您在学习本教程的过程中有所收获。我们构建的应用程序可以作为您自己项目的起点。如果您想继续完善这个留言板,以下是一些功能建议:

  • 在消息中添加评论。这将教你如何在 AppWrite 中创建关联关系。
  • 添加无限滚动或分页功能,以处理无法一次性全部渲染的大量消息。
  • 使用 Next 的服务器端渲染功能,在服务器端而不是客户端获取和渲染消息。

关于部署,我们已经将 AppWrite 配置在一个简洁的 docker-compose 环境中。您可以将其托管在诸如 Google Cloud 或 AWS 等主流云服务提供商上。此外,您还可以参考 Vercel 的这篇指南,将 Next.js 添加到 Docker 环境中。

同时,您还可以使用Preevy为您的项目设置预览环境。这​​样可以轻松地为您的应用程序提供预览环境,您可以与他人共享这些环境,以便快速获得反馈,并保持您的开发工作流程高效运转。

如果你不想采用自托管的方式,AppWrite 也提供云服务,由他们为你托管。但这样一来,你需要将 Next.js 单独托管在 Vercel 或 Netlify 上。

希望您喜欢并觉得本指南有用。

祝你好运!

文章来源:https://dev.to/livecycle/building-a-message-board-with-nextjs-and-appwrite-3910