如何使用XATA和Cloudinary在Next.js中构建在线库
#javascript #nextjs #xata #cloudinary

jamstack,完全被称为JavaScript,API和Markup,是革新我们接近更快,可扩展和安全网站的方式之一。它提供了许多好处,例如更好的性能和出色的开发人员体验,使其易于动态服务静态内容。
在Jamstack体系结构中管理媒体和数据库可能会变得笨拙,因此需要像XATA这样的产品,该产品为Jamstack产品提供了无服务器数据库和Cloudinary,这使得在应用程序中轻松管理媒体。

>

>

在本教程中,我们将创建一个在线库,该图书馆允许我们使用Next.js,Chakra UI,XATA和Cloudinary上传,管理和获取其他书籍。

什么是XATA?

Xata是我们的首选serverless data平台,它使我们能够通过管理,扩展,防止停机,缓存和维护数据库来优化开发工作流程。同时,我们专注于开发客户端。它提供了强大的搜索引擎,关系数据库,分析引擎等。它可以与NextJ,Svelte JS,Node JS等各种JavaScript堆栈一起使用。

什么是云?

Cloudinary是一个媒体管理平台,可轻松管理网站和移动应用程序的图像,文件和视频。它涵盖了上传,存储,优化,交付和媒体转换的所有内容,并提供了各种语言的SDK支持。

先决条件

要遵循本教程,我们将需要以下内容:

  • 理解JavaScript,Typsecript和ES6功能
  • React和Next.js
  • 的基本知识
  • 脉轮UI的基本知识
  • Nodejsnpm安装在我们的PC上
  • 一个云帐户,signup是免费的
  • XATA帐户signup是免费的

该项目当前在code sandbox上运行,您可以轻松分叉开始。
它已在此link上实时部署在Netlify上。

这张我们将要构建的图片

A web display of the bookly app

现在让我们开始构建我们的应用程序。

项目设置

我们将运行npm init以在我们选择的任何目录中初始化一个新项目。

让我们转到项目目录并运行以下命令以安装依赖项。

cd <project_name>

# Install dependencies
npm install react react-dom next

# Install devDependencies
npm install typescript @types/node @types/react @chakra-ui/react axios bcrypt nprogress yup -D

我们将创建一个名为pages的新目录,并添加一个index.tsx文件。在index.tsx中,让我们添加这样的布局。


const Index = ({ }) => {
  return (
    <h1>Hello world</h1>
  )
};

然后,我们通过运行以下命令在http://localhost:3000/上启动开发服务器:

npx next dev

运行此命令将为我们提供一个空白页面,并带有“ Hello World”。

现在,让我们设置应用程序的数据库。

设置XATA和数据库模式

要开始使用XATA,我们将通过命名并选择希望我们的数据库的区域来创建一个新数据库。

A picture of Xata workspace

A picture of selecting database region in Xata

接下来,让我们通过添加新表格开始构建数据库模式。

Xata Schema workspace

我们的模式图看起来像这样:

Schema diagram drawn on drawsql app

然后,我们在XATA Workspace上创建两个称为UserBook的表格,并在上面的模式中添加以下字段。
XATA使我们可以将两个表与type Link联系起来,因此,在添加字段added_by并将其链接到用户表时,我们会使用该表。

现在,让我们在项目中初始化我们的数据库。

要在我们的命令行接口(CLI)上开始使用XATA,我们将通过运行命令在全球安装它。

# Install the Xata CLI globally
npm i -g @xata.io/cli

然后让S运行以下命令以初始化创建的数据库。

xata init --db https://Ubaydah-s-workspace-qb9vvt.us-east-1.xata.sh/db/bookly

注意:我们可以从工作区的配置部分访问我们的数据库基础URL。它遵循格式https://Ubaydah-s-workspace-qb9vvt.{region}.xata.sh/db/{database}

XATA提示我们并为我们的XATA配置生成一个新文件。

Xata codegen on CLI

在此过程中,它提示我们的浏览器打开,因此我们可以为我们的项目创建一个API密钥,该密钥将添加到我们的.env文件中,并且在部署时也需要。

Xata prompting browser to generate API key

代码是在我们的xata.ts文件中生成的,该文件包含XataClient的代码和实例中的模式,我们将用于与XATA数据库进行交互。

我们都设置了。让我们深入研究我们的API。

构建API

在我们的pages目录中,让我们在api文件夹中创建一个名为api的新文件夹,名为bookuser

登录和注册功能

让我们在我们的用户文件夹中创建两个新文件,register.tslogin.ts

register.ts文件中,让我们添加以下代码。


import { NextApiHandler } from "next";
import { getXataClient } from "../../../utils/xata";
import bcrypt from "bcrypt";
import { promisify } from "util";

const hash = promisify(bcrypt.hash);
const handler: NextApiHandler = async (req, res) => {
  const { email, username, password } = req.body;
  const xata = getXataClient();
  //validate
  const emailExists = await xata.db.User.filter({ email }).getFirst();
  const usernameExists = await xata.db.User.filter({ username }).getFirst();
  if (usernameExists) {
    return res.status(400).json({ success: false, message: "Username already exists! " });
  } else if (emailExists) {
    return res.status(400).json({ success: false, message: "User with the email already exists!" });
  }
  // user doesn't exist
  await xata.db.User.create({
    username: username,
    email: email,
    password: await hash(password, 10),
  });
  return res.status(201).json({
    message: "user created",
  });
};
export default handler;

说明:在上面的代码中,我们创建了一个NextApiHandler,该纳税人可以在数据库中处理新用户。我们首先检查了数据库中的电子邮件或用户名是否存在,并基于此错误引发错误。然后,如果用户在数据库中不存在,我们使用bcrypt软件包哈希密码,并在数据库中创建了它们的新实例。

让我们添加我们的登录API。

login.ts文件中,让我们添加以下代码。


import { NextApiHandler } from "next";
import { getXataClient } from "../../../utils/xata";
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
import { promisify } from "util";
import cookie from "cookie";
const { JWT_SECRET_KEY } = process.env;
const compare = promisify(bcrypt.compare);
const handler: NextApiHandler = async (req, res) => {
  const { email, password } = req.body;
  // instantiate a xata client
  const xata = getXataClient();
  const user = await xata.db.User.filter({ email }).getFirst();
  // validate
  if (!(user && (await compare(password, user.password)))) {
    return res.status(400).json({ success: false, message: "Username or password is incorrect!" });
  }
  // create a jwt token that is valid for 7 days
  const token = jwt.sign({ sub: user.id }, JWT_SECRET_KEY, { expiresIn: "7d" });
  user.update({ token: token });
  // set the jwt token as a cookie
  res.setHeader(
    "Set-Cookie",
    cookie.serialize("token", token, {
      maxAge: 24 * 60 * 60, // 1 day
      sameSite: "strict",
      path: "/",
    })
  );
  return res.status(200).json({
    id: user.id,
    username: user.username,
    email: user.email,
    token: token,
  });
};
export default handler;

说明:在上面的代码中,我们从数据库中滤除了用户,检查了用户是否存在以及输入的密码是否与哈希密码相关。如果用户存在,我们为用户创建了一个新的Jwt令牌,有效期为7天,并使用令牌更新了他们的详细信息。然后,我们将Jwt令牌设置为标题中的cookie,在浏览器中将在一天内到期。

添加数据库CRUD功能

现在,让我们编写API来创建,删除,更新和从我们的数据库中获取书籍。

api/book目录中,让我们创建四个名为create.tsupdate.tsget.tsdelete.ts的新文件。

create.ts文件中,让我们添加以下代码:


import { NextApiHandler } from "next";
import { getXataClient } from "../../../utils/xata";
const handler: NextApiHandler = async (req, res) => {
  // instantiate a xata client
  const xata = getXataClient();
  const book = await xata.db.Book.create(req.body);
  res.status(201).json({
    data: book,
  });
};
export default handler;

在上面的代码中,我们创建了从数据库中的请求正文中获得的书的新条目。

update.ts文件中,让我们添加以下代码:


import { NextApiHandler } from "next";
import { getXataClient } from "../../../utils/xata";
const handler: NextApiHandler = async (req, res) => {
  const xata = getXataClient();
  const book = await xata.db.Book.update(req.body);
  res.status(201).json({
    data: book,
  });
};
export default handler;

在上面的代码中,我们在数据库中使用请求正文更新了书的条目。

delete.ts文件中,让我们添加以下代码:


import { NextApiHandler } from "next";
import { getXataClient } from "../../../utils/xata";
const handler: NextApiHandler = async (req, res) => {
  const { id } = req.body;
  const xata = getXataClient();
  await xata.db.Book.delete(id);
  res.end();
};
export default handler;

在这里,我们通过从请求正文中获得的书id删除了数据库中的条目。

get.ts文件中,让我们添加以下代码:


import { NextApiHandler } from "next";
import { getXataClient } from "../../../utils/xata";
const handler: NextApiHandler = async (req, res) => {
  const xata = getXataClient();
  const books = await xata.db.Book.getMany();
  res.status(200).json({
    data: books,
  });
};
export default handler;

在这里,我们查询XATA数据库以获取数据库的所有条目。

我们可以在Xata docs中了解有关查询数据的不同方法的更多信息。

搜索功能

api/book目录中,让我们创建一个名为search.ts的新文件和以下代码:


import { NextApiHandler } from "next";
import { getXataClient } from "../../../utils/xata";
const handler: NextApiHandler = async (req, res) => {
  const xata = getXataClient();
  const books = await xata.db.Book.search(req.body.value, {
    fuzziness: 1,
    prefix: "phrase",
  });
  res.status(200).json({
    data: books,
  });
};
export default handler;

在此代码中,我们在书籍表上执行了全文搜索,以获取与传递值相匹配的条目。 XATA在搜索时可容忍错别字,并且在上面的代码中添加的fuzziness参数启用了此行为。设定的编号1表示一个错别字公差,可以通过将其设置为0来禁用fuzziness

让我们在xata search docs中了解更多信息。

构建了必需的API后,我们设置了Cloudinary帐户以存储我们的文档。

设置云帐户

我们将在Cloudinary上创建一个帐户,并将其重定向到仪表板。让我们复制我们的云名称并将其存储在我们的.env文件中。

cloudinary dashboard

让悬停在设置部分上,然后使用Unsigned键上传来上传我们的文档。

Image description

Image description

之后,让我们浏览安全部门以启用使用帐户的PDF交付。

Image description

现在,我们将使用该帐户上传PDFS。

建立前端和消耗API

在根目录中,让我们创建一个名为src的新文件夹,我们将在此处创建我们的React组件和挂钩。创建名为componentsconfig,hooks的新文件夹。 config文件夹将具有一个config.ts文件,我们的云云名称将被存储。


const config = {
  cloudinary_name: process.env.NEXT_PUBLIC_CLOUD_NAME,
};
export default config;

在挂钩文件夹中,让我们创建一个名为useUploadImage.tsx的新文件,并添加以下代码:


import { useState } from "react";
const useUploadImage = () => {
  const [preview, setPreview] = useState<string>("");
  const [file, setFile] = useState(null);
  const onChange = (e: any, multiple?: any) => {
    //check if files are multiple and collect them in an array
    if (multiple) {
      const selectedFIles: any = [];
      const targetFiles = e.target.files;
      const targetFilesObject = [...targetFiles];
      targetFilesObject.map((file) => {
        return selectedFIles.push({ file: file, url: URL.createObjectURL(file) });
      });
      setPreview(selectedFIles);
    } else {
      const file = e.target.files[0];
      const reader: any = new FileReader();
      setFile(e.target.files[0]);
      reader.onloadend = () => setPreview(reader.result);
      reader.readAsDataURL(file);
    }
  };
  const image = {
    preview,
    file,
  };
  return { onChange, image };
};
export default useUploadImage;

上面的代码收集在客户端发送的所有文件,因此我们可以轻松地将其发送到Cloudinary。

在组件文件夹中,我们将创建一个名为BookForm.tsx的新组件,我们将处理创建和更新书籍并将其发送到next.js服务器。

在上面的代码中,我们使用koude47创建了一些表单验证。然后,我们为书籍创建了一些初始值。 useEffect获取当前用户登录并将其存储在我们的本地存储中。处理该书籍到服务器的处理功能具有两个参数:发送的数据和isEditisEdit可以帮助我们区分我们是添加新书还是对其进行更新。然后创建一个新的FormData,使用koude53发送到Cloudinary API中的文件和upload_preset。我们的安全URL是从云响应中获得的,云响应与其他数据一起发送到API以创建新书。

查看GitHub repository上的完整代码,以检查其他实现,例如登录和注册表单。

该代码还在code sandbox上运行,并且该代码在Netlify上部署。

结论

在本教程中,我们了解了无服务器数据库以及如何使用XATA创建一个数据库。我们还了解了Cloudinary以及如何使用上传预设中的Cloudinary上传文件。

下一步

查看完整的GitHub repository,以查看其他实现和组件。
我们还可以添加额外的功能,例如显示用户配置文件,将书籍为列表添加书签以及其他改进应用程序的想法。

资源