在本教程中,您将学习到应用部署平台 Fly.io 和全球分布式的 S3 兼容对象存储服务 Tigris。
这两个平台密切相关,使它们成为您项目的绝佳选择。您可以从 Fly.io 获得应用部署体验,并从 Tigris 获得对象存储功能。
应用部署相当简单易懂,因此让我们首先快速介绍一下 Tigris 使用的存储桶存储。
(本文视频讲解:java567.com)
先决条件
- 安装了 IDE/代码编辑器,如 Visual Studio Code
- 安装了 Node.js 和 npm
- 安装或设置了 Next.js
- 在 Fly.io 上有一个免费账户
- 在 Tigris 上有一个免费账户
目录
- 什么是存储桶?
- 我们将要构建什么
- 如何在 Fly.io 和 Tigris 上创建账户
- 如何设置用户数据库项目
- 如何构建用户数据库应用程序
- 如何创建用户数据库界面
- 如何将您的应用程序部署到 Fly.io
- 结论
什么是存储桶?
Amazon S3 存储桶是通过亚马逊网络服务(AWS)的简单存储服务(S3)平台访问的公共云存储资源。
全球分布的、与 S3 兼容的对象存储服务 Tigris 使用低延迟存储功能。这意味着您可以在 Tigris 上访问亚马逊的 S3 存储桶来满足您的存储需求。
Tigris 还已经与 Fly.io 完全集成,并且也与运行在 Fly.io 硬件上的 flyctl 完全集成。Fly.io 的命令行界面 flyctl 允许您从账户创建到应用程序部署的所有操作都可以处理。
我们将构建什么
为了学习这些平台的基础知识,我们将构建一个用户数据库应用程序。这相当直观:基本上我们可以执行完整的 CRUD 请求,这意味着可以读取、添加、更新和删除用户数据。
Next.js 将是我们的主要框架,因为它允许我们构建全栈应用,而无需创建单独的服务器。
用户数据库应用首页
您可以从他们的文档了解更多关于 Fly.io 和 Tigris 的信息。为了这个项目,我们需要在两个平台上创建一个账户,我会在一分钟内为您介绍。
所以现在,理论部分已经讲解完毕,让我们开始吧。
您可以在我的 GitHub 上找到这个项目的代码库。
如何在 Fly.io 和 Tigris 上创建账户
只需按照以下步骤在两个平台上开始运行:
- 首先您需要在 Fly.io 上创建一个账户,因为要使用 Tigris 您需要一个 Fly.io 的账户。
- 接下来,在您的计算机上安装 flyctl 命令行工具,这对于设置您的账户以部署应用程序至关重要。
好的,让我们继续下一个阶段,我们将在那里设置我们的项目以及我们的 Tigris 存储桶。
如何设置用户数据库项目
首先,导航到您计划创建项目的计算机上的目录。然后创建一个名为 fly-tigris-user-database
的文件夹并进入它。现在在该文件夹内运行以下命令来设置一个 Next.js 项目:
npx create-next-app .
我们正在做的就是设置我们的 Next.js 项目,重要的是对配置选择是为 Tailwind CSS 和 App router 选择是,因为我们在这个项目中需要它们。
现在运行以下命令来安装 AWS SDK:
npm install @aws-sdk/client-s3
我们只需要安装一个包(@aws-sdk/client-s3
),这是连接到我们的存储桶所需的。
好的,很好 - 现在是时候为我们刚刚创建的项目创建一个存储桶了。您可以在它们的官方文档中找到此过程的指导。
只需运行以下命令来创建一个存储桶:
fly storage create
现在在设置屏幕上,选择一个名称给您的存储桶。名称应该是唯一的,所以您不能使用其他人选择的名称。
好的,现在是最重要的阶段:您应该有您的 AWS 和存储桶的密钥,就像这个例子中所示:
AWS_ACCESS_KEY_ID: 您的密钥
AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev
AWS_REGION: auto
AWS_SECRET_ACCESS_KEY: 您的秘密访问密钥
BUCKET_NAME: 您的存储桶名称
在您的 Next.js 项目根目录下创建一个 .env.local
文件,然后将所有这些秘密环境变量复制粘贴进去。
为了使这些环境变量在我们的 Next.js 应用程序中正常工作,我们需要调整它们的名称使其公开。查看下面的示例,并对您的 .env.local
文件进行更改。
NEXT_PUBLIC_SECRET_AWS_ACCESS_KEY_ID: 您的密钥
NEXT_PUBLIC_SECRET_AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev
NEXT_PUBLIC_SECRET_AWS_REGION: auto
NEXT_PUBLIC_SECRET_AWS_SECRET_ACCESS_KEY: 您的秘密访问密钥
NEXT_PUBLIC_SECRET_BUCKET_NAME: 您的存储桶名称
现在在 Tigris 文档页面上,如果您点击仪表板按钮并登录您的账户,您应该会看到您新创建的存储桶,就像我的示例中所示:
Tigris 网站存储桶管理页面
太棒了!第一阶段完成了。我们现在有一个存储桶来在线存储我们的应用数据,所以我们可以开始在下一节中创建我们的应用程序。
如何构建用户数据库应用程序
我将把这一部分分成两个部分。首先,我们将建立并运行服务器,以便测试 CRUD 端点。然后我们将完成前端部分。
如何创建用户数据库服务器
首先,让我们创建我们的后端架构。我们将创建四个端点,一个用于每个 CRUD 请求。我们还需要一个辅助文件,其中包含一些用于从我们的对象存储中获取用户的函数。
如果您尚未这样做,请cd
到项目的根目录,并运行下面的命令。它们将快速设置我们的所有文件和文件夹:
cd src/app
mkdir api
mkdir api/deleteuser api/getusers api/postuser api/updateuser
touch api/deleteuser/route.js
touch api/getusers/route.js
touch api/postuser/route.js
touch api/updateuser/route.js
mkdir helpers
touch helpers/getUsers.js
好的,很快就完成了。现在我们只需要将代码添加到我们的五个文件中,我们的后端 API 就准备好测试了。
首先让我们处理辅助文件。这个文件中的代码允许我们收集并访问存储在 Tigris 上我们 S3 存储桶中的用户数据。
把下面的代码放到 helpers/getUsers.js
中:
import {S3Client,ListObjectsV2Command,GetObjectCommand,
} from '@aws-sdk/client-s3';const streamToString = (stream) =>new Promise((resolve, reject) => {const chunks = [];stream.on('data', (chunk) => chunks.push(chunk));stream.on('error', reject);stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));});export async function fetchAllUsersFromS3() {try {const s3 = new S3Client({region: process.env.NEXT_PUBLIC_SECRET_AWS_REGION,endpoint: process.env.NEXT_PUBLIC_SECRET_AWS_ENDPOINT_URL_S3,credentials: {accessKeyId: process.env.NEXT_PUBLIC_SECRET_AWS_ACCESS_KEY_ID,secretAccessKey: process.env.NEXT_PUBLIC_SECRET_AWS_SECRET_ACCESS_KEY,},});const commandDetails = new ListObjectsV2Command({Bucket: process.env.NEXT_PUBLIC_SECRET_BUCKET_NAME,MaxKeys: 10,});const { Contents } = await s3.send(commandDetails);console.log('List Result', Contents);if (!Contents) {console.log('no users');} else {const users = await Promise.all(Contents.map(async (item) => {const getObject = new GetObjectCommand({Bucket: process.env.NEXT_PUBLIC_SECRET_BUCKET_NAME,Key: item.Key,});const { Body } = await s3.send(getObject);const data = await streamToString(Body);const userObject = JSON.parse(data);console.log('Data', data);return userObject;}));return users;}} catch (e) {console.error(e);throw e;}
}export async function getUserById(users, userId) {if (!users) {console.log('no users');} else {return users.find((user) => user.id === userId);}
}export async function getUserByIdEmail(users, email) {if (!users) {console.log('no users');} else {return users.find((user) => user.email.toLowerCase() === email.toLowerCase());}
}
主要的函数是 fetchAllUsersFromS3()
,它基本上使用我们需要的凭证和配置创建 S3 客户端。它使用 GetObjectCommand
获取对象的内容,然后使用 streamToString
函数将其从流转换为字符串。然后解析 JSON 数据返回用户对象。
另外两个函数调用,getUserById(users, userId)
和 getUserByIdEmail(users, email)
,是允许我们根据 ID 或电子邮件地址在我们的 S3 存储桶中搜索用户的辅助函数。代码将基本的 AWS 配置参数存储在环境变量中,包括区域、端点 URL、访问密钥和秘密访问密钥,以及 S3 存储桶名称。
好了,现在只剩下路由了。
首先把这段代码放到 getusers/route.js
中:
import {S3Client,ListObjectsV2Command,GetObjectCommand,
} from '@aws-sdk/client-s3';export async function GET() {const streamToString = (stream) =>new Promise((resolve, reject) => {const chunks = [];stream.on('data', (chunk) => chunks.push(chunk));stream.on('error', reject);stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));});try {const s3 = new S3Client({region: process.env.NEXT_PUBLIC_SECRET_AWS_REGION,endpoint: process.env.NEXT_PUBLIC_SECRET_AWS_ENDPOINT_URL_S3,credentials: {accessKeyId: process.env.NEXT_PUBLIC_SECRET_AWS_ACCESS_KEY_ID,secretAccessKey: process.env.NEXT_PUBLIC_SECRET_AWS_SECRET_ACCESS_KEY,},});const listParams = {Bucket: process.env.NEXT_PUBLIC_SECRET_BUCKET_NAME,MaxKeys: 10,};const list = new ListObjectsV2Command(listParams);const { Contents } = await s3.send(list);console.log('List Result', Contents);if (!Contents || Contents.length === 0) {console.log('No users found');return new Response(JSON.stringify({ error: 'No users found' }), {status: 404,});}const users = await Promise.all(Contents.map(async (item) => {const getObjectParams = {Bucket: process.env.NEXT_PUBLIC_SECRET_BUCKET_NAME,Key: item.Key,};const getObject = new GetObjectCommand(getObjectParams);const { Body } = await s3.send(getObject);const data = await streamToString(Body);console.log('Backend API GET Data:', data);return JSON.parse(data);}));return new Response(JSON.stringify(users), { status: 200 });} catch (e) {console.error('Error:', e);return new Response(JSON.stringify({ error: e.message || 'Unknown error' }),{ status: 500 });}
}
在这个文件的代码中,我们从 Tigris S3 存储桶中检索用户数据,并通过 Next.js API 路由处理程序函数将其作为 JSON 响应返回。
该函数还导入了与我们的 S3 存储桶通信所需的 AWS SDK 客户端。当请求 API 路由时,将调用主入口点,即 GET
函数。使用环境变量,GET
方法首先建立了一个 S3 客户端,其中包含所需的区域、端点和凭证设置。
之后,创建了一个 ListObjectsV2Command
来从指定的 S3 存储桶中检索用户数据列表的项目。然后方法通过对象列表,使用 GetObjectCommand
获取每个对象的数据。
streamToString
方法用于将每个对象的内容从流转换为字符串。
在解析 JSON 输入后,用户对象作为 JSON 响应发送出去。如果未检测到用户,则返回一个 404 错误响应,并提供一个错误消息。如果在过程中出现任何问题,则提供一个 500 错误响应,其中包含错误消息。
接下来是 POST 路由,将以下代码放入 postuser/route.js
中:
import { fetchAllUsersFromS3, getUserByIdEmail } from '../../helpers/getUsers';import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';export async function POST(req) {try {const { firstname, lastname, email, password } = await req.json();const id = crypto.randomUUID();const data = { firstname, lastname, email, password, id };console.log('Request body data', data);const allUsers = await fetchAllUsersFromS3();console.log('all users', allUsers);const existingUser = await getUserByIdEmail(allUsers, email);console.log(existingUser, email);if (existingUser) {return Response.json({error: 'Email address already in use',});}const s3 = new S3Client({region: process.env.NEXT_PUBLIC_SECRET_AWS_REGION,endpoint: process.env.NEXT_PUBLIC_SECRET_AWS_ENDPOINT_URL_S3,credentials: {accessKeyId: process.env.NEXT_PUBLIC_SECRET_AWS_ACCESS_KEY_ID,secretAccessKey: process.env.NEXT_PUBLIC_SECRET_AWS_SECRET_ACCESS_KEY,},});const commandDetails = new PutObjectCommand({Body: JSON.stringify(data),Bucket: process.env.NEXT_PUBLIC_SECRET_BUCKET_NAME,Key: email,});await s3.send(commandDetails);return Response.json({ message: 'User added' });} catch (e) {console.error(e);return Response.json({ error: 'Failed to create user' });}
}
此代码用于管理用户注册,并将用户信息保存在我们的 Tigris S3 存储桶中。fetchAllUsersFromS3
和 getUserByIdEmail
这两个辅助函数被导入,用于与 S3 存储桶通信并收集用户数据。初始入口点,即当通过 API 路由发出 POST
请求时调用的是 POST
函数。
首先,POST
函数从请求体中提取用户信息(名、姓、电子邮件和密码)。然后使用 crypto.randomUUID()
创建一个唯一的用户 ID。为了获取 S3 存储桶中的所有当前用户数据,该方法调用 fetchAllUsersFromS3
辅助函数。
代码使用 getUserByIdEmail
辅助函数来确定请求中提供的电子邮件地址是否已经存在于用户数据中。如果电子邮件地址已经存在,则该方法提供一个包含错误消息的 JSON 响应。如果电子邮件地址是唯一的,则使用环境变量创建具有所需设置的 S3 客户端(区域、端点和凭证)。
然后生成一个 PutObjectCommand
,将新的用户数据(作为 JSON 字符串)上传到 S3 存储桶,其中电子邮件地址是键。最后,该方法生成一个 JSON 响应,确认用户已成功添加。如果在过程中发生错误,则该函数提供一个包含错误消息的 JSON 响应。
紧接着是我们的 UPDATE 路由,将以下代码放入 updateuser/route.js
中:
import { getUserById, fetchAllUsersFromS3 } from '../../helpers/getUsers';import {S3Client,DeleteObjectCommand,PutObjectCommand,
} from '@aws-sdk/client-s3';export async function PUT(req) {try {const { firstname, lastname, email, originalEmail, id } = await req.json();console.log('request data', firstname, lastname, email, originalEmail, id);const allUsers = await fetchAllUsersFromS3();console.log('all users', allUsers);const userToUpdate = await getUserById(allUsers, id);console.log('user to update', userToUpdate);const user = allUsers.find((user) => user.id === id);const userEmail = user ? user.email : null;console.log('User Email', userEmail);if (!userToUpdate) {return Response.json({ error: 'User not found' });}if (!originalEmail || !email) {return Response.json({error: 'Both originalEmail and email are required for update',});}const data = { firstname, lastname, email, id };console.log('Updated data', data);const s3 = new S3Client({region: process.env.NEXT_PUBLIC_SECRET_AWS_REGION,endpoint: process.env.NEXT_PUBLIC_SECRET_AWS_ENDPOINT_URL_S3,credentials: {accessKeyId: process.env.NEXT_PUBLIC_SECRET_AWS_ACCESS_KEY_ID,secretAccessKey: process.env.NEXT_PUBLIC_SECRET_AWS_SECRET_ACCESS_KEY,},});console.log('Original email', originalEmail);console.log('New email', email);if (userEmail === originalEmail) {console.log('The emails are the same so its a match');const deleteCommand = new DeleteObjectCommand({Bucket: process.env.NEXT_PUBLIC_SECRET_BUCKET_NAME,Key: originalEmail,});await s3.send(deleteCommand);const putCommand = new PutObjectCommand({Body: JSON.stringify(data),Bucket: process.env.NEXT_PUBLIC_SECRET_BUCKET_NAME,Key: email,});await s3.send(putCommand);return Response.json({ message: 'User updated successfully' });} else {console.log('Error: The emails do not match');return Response.json({ error: 'Failed to update user' });}} catch (e) {console.error(e);}
}
该代码包括之前的相同辅助函数 getUserById
和 fetchAllUsersFromS3
,用于连接 S3 存储桶并获取用户数据。主要入口点是 PUT
函数,每当向 API 路由发出 PUT
请求时就会调用它。PUT
函数首先从请求体中提取用户数据(名、姓、电子邮件、原始电子邮件和 ID)。
然后它调用 fetchAllUsersFromS3
辅助函数,从 S3 存储桶中检索所有现有的用户数据。然后代码通过使用指定的用户 ID 调用 getUserById
辅助函数来找到要更新的用户。如果找不到用户,则该方法提供一个包含错误消息的 JSON 响应。
如果原始电子邮件或电子邮件不存在,则该函数提供一个包含错误消息的 JSON 响应。然后方法根据提供的信息构建一个更新后的用户数据对象。此外,代码可以使用环境变量创建具有所需设置(区域、端点和凭证)的 S3 客户端。
假设初始电子邮件地址与当前用户的电子邮件地址匹配,该函数将更新用户信息;但是如果原始电子邮件与当前用户的电子邮件地址不匹配,则方法提供一个包含错误消息的 JSON 响应。像之前一样,如果在过程中检测到错误,则该函数将创建一个日志而不发送响应。
最后是我们的 DELETE 路由。将以下代码添加到 deleteuser/route.js
中:
import { S3Client, DeleteObjectCommand } from '@aws-sdk/client-s3';import { fetchAllUsersFromS3, getUserById } from '../../helpers/getUsers';export async function DELETE(req) {try {const id = await req.json();console.log('Id', id.id);const allUsers = await fetchAllUsersFromS3();console.log('all users', allUsers);const userToDelete = await getUserById(allUsers, id.id);console.log('user to delete', userToDelete);if (!userToDelete) {return Response.json({ error: 'User not found' });}const userEmail = userToDelete.email;const s3 = new S3Client({region: process.env.NEXT_PUBLIC_SECRET_AWS_REGION,endpoint: process.env.NEXT_PUBLIC_SECRET_AWS_ENDPOINT_URL_S3,credentials: {accessKeyId: process.env.NEXT_PUBLIC_SECRET_AWS_ACCESS_KEY_ID,secretAccessKey: process.env.NEXT_PUBLIC_SECRET_AWS_SECRET_ACCESS_KEY,},});const deleteCommand = new DeleteObjectCommand({Bucket: process.env.NEXT_PUBLIC_SECRET_BUCKET_NAME,Key: userEmail,});await s3.send(deleteCommand);return Response.json({ message: 'User deleted successfully' });} catch (e) {console.error(e);return Response.json({ error: 'Failed to delete user' });}
}
这用于从我们的存储桶中删除数据。代码包括与 S3 通信所需的相同的 AWS SDK 客户端,以及用于从 S3 存储桶中获取用户数据的两个辅助函数 fetchAllUsersFromS3
和 getUserById
。关键入口点是 DELETE
函数,当向 API 路由发出 DELETE
请求时调用它。
在 DELETE
函数中,首先从请求体中提取用户 ID。然后它调用 fetchAllUsersFromS3
辅助方法,从 S3 存储桶中检索所有现有的用户数据。然后代码通过使用指定的用户 ID 调用 getUserById
辅助函数来找到要删除的用户。如果客户端找不到,则该方法提供一个包含错误消息的 JSON 响应。
然后生成一个 DeleteObjectCommand
,从 S3 存储桶中删除该项,其中用户的电子邮件地址是键。最后,该方法生成一个 JSON 响应,确认用户已成功删除。如果在过程中检测到错误,则该函数将创建一个日志而不发送响应。
前端部分
现在我们已经创建了我们的 API,我们将构建一个简单的前端来测试我们的功能。我们将使用 React 和 Tailwind CSS 来实现。
首先,确保您已经安装了 create-next-app
。如果没有,请运行以下命令:
npm install -g create-next-app
接下来,在终端中运行以下命令创建一个新的 Next.js 项目:
npx create-next-app@latest my-next-app
现在,我们将进入项目文件夹并安装一些依赖项:
cd my-next-app
npm install aws-sdk tailwindcss
接下来,我们将配置 Tailwind CSS。在项目根目录下创建一个 tailwind.config.js
文件,内容如下:
module.exports = {purge: [],darkMode: false, // or 'media' or 'class'theme: {extend: {},},variants: {extend: {},},plugins: [],
}
然后,在 styles/globals.css
中,添加以下内容:
@tailwind base;
@tailwind components;
@tailwind utilities;
接下来,让我们在项目中创建一个简单的注册表单。在 pages
目录下创建一个名为 index.js
的文件,并将以下代码粘贴到该文件中:
import { useState } from 'react';export default function Home() {const [form, setForm] = useState({firstname: '',lastname: '',email: '',password: '',});const handleChange = (e) => {const { name, value } = e.target;setForm((prevState) => ({...prevState,[name]: value,}));};const handleSubmit = async (e) => {e.preventDefault();try {const response = await fetch('/api/postuser', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify(form),});if (response.ok) {const data = await response.json();console.log(data);}} catch (error) {console.error('Error:', error);}};return (<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8"><div className="max-w-md w-full space-y-8"><div><h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Register</h2></div><form className="mt-8 space-y-6" onSubmit={handleSubmit}><input type="hidden" name="remember" defaultValue="true" /><div className="rounded-md shadow-sm -space-y-px"><div><label htmlFor="firstname" className="sr-only">First name</label><inputid="firstname"name="firstname"type="text"autoComplete="given-name"requiredclassName="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"placeholder="First name"value={form.firstname}onChange={handleChange}/></div><div><label htmlFor="lastname" className="sr-only">Last name</label><inputid="lastname"name="lastname"type="text"autoComplete="family-name"requiredclassName="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"placeholder="Last name"value={form.lastname}onChange={handleChange}/></div><div><label htmlFor="email" className="sr-only">Email address</label><inputid="email"name="email"type="email"autoComplete="email"requiredclassName="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"placeholder="Email address"value={form.email}onChange={handleChange}/></div><div><label htmlFor="password" className="sr-only">Password</label><inputid="password"name="password"type="password"autoComplete="current-password"requiredclassName="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"placeholder="Password"value={form.password}onChange={handleChange}/></div></div><div><buttontype="submit"className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"><span className="absolute left-0 inset-y-0 flex items-center pl-3">{/* Heroicon name: solid/lock-closed */}<svgclassName="h-5 w-5 text-indigo-500 group-hover:text-indigo-400"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 20 20"fill="currentColor"aria-hidden="true"><pathfillRule="evenodd"d="M10 12a2 2 0 100-4 2 2 0 000 4z"/><pathfillRule="evenodd"d="M4 8a6 6 0 1112 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2V8zm2-2v5a2 2 0 002 2h6a2 2 0 002-2V6a2 2 0 00-2-2H8a2 2 0 00-2 2z"clipRule="evenodd"/></svg></span>Register</button></div></form></div></div>);
}
这将创建一个简单的注册表单,允许用户输入名、姓、电子邮件和密码。提交表单将通过 API 路由将用户数据发送到我们的 Next.js 应用程序的服务器端。
现在,您可以运行您的 Next.js 应用程序并测试 API 功能。要运行应用程序,请在终端中执行以下命令:
npm run dev
这将启动本地开发服务器。现在,您可以打开浏览器并访问 http://localhost:3000,然后尝试在注册表单中输入数据并提交以查看 API 是否正常工作。
完成这个项目后,您可以进一步扩展它,例如,创建登录表单、更多 API 路由以支持其他操作,或是添加用户数据的验证。
(本文视频讲解:java567.com)