如何使用 Fly.io 和 Tigris 部署 Next.js 应用

在本教程中,您将学习到应用部署平台 Fly.io 和全球分布式的 S3 兼容对象存储服务 Tigris。

这两个平台密切相关,使它们成为您项目的绝佳选择。您可以从 Fly.io 获得应用部署体验,并从 Tigris 获得对象存储功能。

应用部署相当简单易懂,因此让我们首先快速介绍一下 Tigris 使用的存储桶存储。

(本文视频讲解:java567.com)

先决条件

  • 安装了 IDE/代码编辑器,如 Visual Studio Code
  • 安装了 Node.js 和 npm
  • 安装或设置了 Next.js
  • 在 Fly.io 上有一个免费账户
  • 在 Tigris 上有一个免费账户

目录

  1. 什么是存储桶?
  2. 我们将要构建什么
  3. 如何在 Fly.io 和 Tigris 上创建账户
  4. 如何设置用户数据库项目
  5. 如何构建用户数据库应用程序
  6. 如何创建用户数据库界面
  7. 如何将您的应用程序部署到 Fly.io
  8. 结论

什么是存储桶?

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 上创建账户

只需按照以下步骤在两个平台上开始运行:

  1. 首先您需要在 Fly.io 上创建一个账户,因为要使用 Tigris 您需要一个 Fly.io 的账户。
  2. 接下来,在您的计算机上安装 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 存储桶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 存储桶中。fetchAllUsersFromS3getUserByIdEmail 这两个辅助函数被导入,用于与 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);}
}

该代码包括之前的相同辅助函数 getUserByIdfetchAllUsersFromS3,用于连接 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 存储桶中获取用户数据的两个辅助函数 fetchAllUsersFromS3getUserById。关键入口点是 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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/2417.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2001-2021年上市公司制造业智能制造词频统计数据

2001-2021年上市公司制造业智能制造词频统计数据 1、时间&#xff1a;2001-2021年 2、来源&#xff1a;上市公司年报 3、指标&#xff1a;年份、股票代码、行业名称、行业代码、所属省份、所属城市、智能制造词频、智能制造占比(%) 4、范围&#xff1a;上市公司 5、样本量…

探索嵌入式系统:快速入门指南概览

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

OpenHarmony实战开发-性能测试工具SmartPerf Editor使用指导

概述 SmartPerf Editor是一款PC端桌面应用&#xff0c;通过监测、采集应用运行时FPS、CPU、GPU、Memory、Battery、Network等性能数据&#xff0c;帮助开发者了解应用的性能状况。SmartPerf Editor还集成了DrawingDoc功能&#xff0c;可录制Render Service绘制指令&#xff0c…

Android Perfetto 监控应用启动耗时

Perfetto 是一个 Google 开发的用于安卓系统性能监控和调试的工具&#xff0c;它旨在提供实时数据收集和可视化功能&#xff0c;帮助我们分析和优化应用程序的性能表现。Perfetto 可以捕获系统事件、CPU、内存、网络、GPU 等性能指标数据&#xff0c;并将其记录为轻量级的 Trac…

开发环境中的调试视图(IDEA)

当程序员写完一个代码时必然要运行这个代码&#xff0c;但是一个没有异常的代码却未必满足我们的要求&#xff0c;因此就要求程序员对已经写好的代码进行调试操作。在之前&#xff0c;如果我们要看某一个程序是否满足我们的需求&#xff0c;一般情况下会对程序运行的结果进行打…

茶饮门店本地生活抖音团购运营方案计划书

【干货资料持续更新&#xff0c;以防走丢】 茶饮门店本地生活抖音团购运营方案计划书 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 PPT可编辑80页&#xff08;完整资料包含以下内容&#xff09; 目录 抖音本地生活运营方案 1. 账号基础搭建与优化 - 门店账号…

Php-WebView 现代跨平台 GUI分享

GitHub :php-webview 一个用于 C/C 的小型跨平台 Web 视图库&#xff0c;用于构建现代跨平台 GUI。 该项目的目标是为最广泛使用的平台创建一个通用的 HTML5 UI 抽象层。 它支持双向 JavaScript 绑定&#xff08;从 C/C 调用 JavaScript 和从 JavaScript 调用 C/C&#xff09;。…

ClickHouse 高可用之副本

文章目录 ClickHouse 副本支持副本的引擎配置高可用副本副本应用1.副本表概述2.创建副本表3.写入模拟数据4.副本验证 扩展 —— 在 Zookeeper 中查看副本表信息 ClickHouse 副本 ClickHouse 通过副本机制&#xff0c;可以将数据拷贝存储在不同的节点上。这样&#xff0c;如果一…

我的世界服务器设计思路应该是什么样?

我的世界服务器设计思路可以从这4个方面展开&#xff1a;1.选择你喜欢的东西&#xff1b;2.认识你的极限&#xff1b;3.注入新鲜元素&#xff1b;4.让服务器变得享受且有回报。 1.选择你喜欢的东西 设计服务器的首要规则是创造一些你自己会积极享受玩的东西。没有人愿意花费宝…

在Spring boot中指定随机可用的端口

​ 正常情况下每个spring boot启动都有固定的端口&#xff0c;也就是8080&#xff0c;如果启动多个项目&#xff0c;很容易出现端口冲突&#xff0c;那么怎么解决这个问题呢&#xff1f; 解决方案1&#xff1a; random 随机端口 ​ 在spring boot中&#xff0c;可以通过${ran…

linux的一些实用操作

快捷键 强制停止 ctrlc强制停止或退出命令的输入 退出登出 ctrld强制退出用户登录或退出某些程序的专属页面&#xff08;如py&#xff09; ps&#xff1a;不能退出vi/vim 历史命令搜索 history可以查看历史命令&#xff0c;用来复制粘贴 在使用history之后&#xff0c;…

fnm:Rust开发的高效Node版本管理工具

简介 fnm 是一个基于 Rust 开发的 Node 版本管理工具&#xff0c;它的目标是提供一个快速、简单且可靠的方式来管理 Node.js 的不同版本。同时&#xff0c;它是跨平台的&#xff0c;支持 macOS、Linux、Windows。&#x1f680; Fast and simple Node.js version manager, buil…

pycharm-git 配置(1)

1.安装git2.pycharm 中配置git 插件 弹出Git版本号&#xff0c;即配置成功。3.创建本地仓库 VCS->VCS operations->create repository->设置本地目录 左下角可以看到git本地仓库git可以看到push,commit。 4.配置远方仓库&#xff0c;此时确保git上是有这个项目…

【linux】chmod权限开放(整个文件夹)

文章目录 起因权限查看权限修改 失败权限修改成功 起因 想要共享conda环境给同事&#xff0c;发现同事没权限。 权限查看 ls #查看当前目录 ls -l # 查看当前目录的东西和权限正常情况下是显示 三个rwx分别属于user&#xff0c;group&#xff0c;others 前面第一个rwx 是针…

美容预约小程序:简单三步,开启高效预约模式

在当今的数字化时代&#xff0c;一个小程序可以极大地提高美容院的效率和客户满意度。下面我们将详细说明如何通过以下步骤来搭建一个美容院预约小程序。 首先&#xff0c;你需要注册并登录到乔拓云网&#xff0c;这是 一个在线平台&#xff0c;可以帮助你快速创建并管理你的小…

腾讯云服务器,部署mysql数据库后无法远程访问?

一&#xff0c;首先确定自己部署的数据库&#xff0c;是否可以正常登录&#xff0c;验证部署是否是否成功 mysql -u root -p二、放开mysql远程访问权限&#xff0c;依次输入这些命令 create user root% identified with mysql_native_password by xxxxx; grant all privilege…

k8s学习(三十六)centos下离线部署kubernetes1.30(单主节点)

文章目录 服务器准备工作一、升级操作系统内核1 查看操作系统和内核版本2 下载内核离线升级包3 升级内核4 确认内核版本 二、修改主机名/hosts文件1 修改主机名2 修改hosts文件 三、关闭防火墙四、关闭SELINUX配置五、时间同步1 下载NTP2 卸载3 安装4 配置4.1 主节点配置4.2 从…

Linux sudo suid提权练习

题目比较简单&#xff0c;可以利用sudo和多种suid程序提权&#xff0c;做个记录 进入靶场题目环境 获得节点信息 远程连接上 执行命令id&#xff0c;发现只是admin普通账户 sudo提权 发现存在 /usr/bin/vim, /usr/bin/bash, /usr/bin/more, /usr/bin/less, /usr/bin/nano, /…

计算机java项目|springboot校园一卡通

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

网络爬虫快速入门及爬取百度搜索结果(附源码)

前言 爬虫的基本结构及工作流程 1. 确定目标 首先&#xff0c;确定你想要爬取的目标&#xff0c;包括目标网站或网页、需要提取的数据类型&#xff08;如文本、图片、视频等&#xff09;以及爬取的深度&#xff08;单页、整个网站等&#xff09;。 2. 获取网页内容 使用HT…