<返回更多

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

2022-07-12    qaseven
加入收藏

在本文中,我们将学习如何使用 Next.js、 Prisma、 Postgres 和 Fastify 构建一个 Full-stack 应用程序。

在本文中,我们将学习如何使用 Next.js、 Prisma、 Postgres 和 Fastify 构建一个 Full-stack 应用程序。我们将建立一个考勤管理演示应用程序,管理员工的考勤。这个应用程序的流程很简单: 管理用户登录,创建当天的出勤表,然后每个员工在出勤表上进出。

什么是 Next.js?

Next.Js 是一个灵活的 React 框架,它为您提供了创建快速 Web 应用程序的构建块。它通常被称为全栈 React 框架,因为它可以让前端和后端应用程序在相同的代码基上使用无服务器函数来实现这一点。

什么是 Prisma?

Prisma 是一个开源的、 Node.js 和 Typecript ORM,它极大地简化了 SQL 数据库的数据建模、迁移和数据访问。在撰写本文时,Prisma 支持以下数据库管理系统: PostgreSQL、 MySQL、 MariaDB、 SQLite、 AWS Aurora、 Microsoft SQL Server、 Azure SQL 和 MongoDB。您可能还希望单击此处查看所有受支持的数据库管理系统的列表。

什么是 Postgres?

Postgres 也被称为 PostgreSQL,它是一个免费的开源关联式资料库管理系统。它是 SQL 语言的超集,具有许多特性,使开发人员能够安全地存储和扩展复杂的数据工作负载。

先决条件

本教程是一个实践演示教程。因此,最好在你的电脑上安装以下软件:

这个教程的代码可以在 Github 上找到,所以你可以克隆它,然后跟着学习。

https://github.com/Claradev32/attendance

项目设置

让我们从设置 Next.js 应用程序开始。

Shell

npx create-next-App@latest

 

等待安装完成,然后运行下面的命令来安装我们的依赖项。

Shell

yarn add fastify fastify-nextjs iron-session @prisma/clientbryarn add prisma nodemon --dev

 

等待安装完成。

设置 Next.js 和 Fastify

 

默认情况下,Next.js 不使用 Fastify 作为其服务器。要使用 Fastify 为我们的 Next.js 应用程序提供服务,请编辑 package.json 文件中的脚本字段,其代码片段如下所示。

JSON

scripts": {
	"dev": "nodemon server.js",
	"build": "next build",
	"start": "next start",
	"lint": "next lint"
}

 

创建我们的 Fastify 服务器

现在让我们创建一个 server.js 文件。这个文件是我们的应用程序的入口点,然后我们添加了一个需求(‘ fast tify-nextjs’)来包含一个插件,这个插件在 Fastify 公开了 Next.js API 来处理渲染。

打开 server.js 文件,并添加以下代码段:

const fastify = require('fastify')()
async function noOpParser(req, payload) {
return payload;
}
fastify.register(require('fastify-nextjs')).after(() => {
fastify.addContentTypeParser('text/plain', noOpParser);
fastify.addContentTypeParser('application/json', noOpParser);
fastify.next('/*')
fastify.next('/api/*', { method: 'ALL' });
})
fastify.listen(3000, err => {
if (err) throw err
console.log('Server listening on <http://localhost:3000>')
})

在上面的代码片段中,我们使用了 fast-nextjs 插件,它在 Fastify 公开了处理呈现的 Next.js API。然后,我们使用 noopParser 函数解析传入的请求,这使得请求体可用于我们的 Next.js API 路由处理程序,我们使用[ fasttify.next ](< http://fastify.next > 命令)为我们的应用定义两个路由。然后我们创建 Fastify 服务器,让它监听端口3000。

现在继续并使用纱线 dev 命令运行应用程序: 应用程序将在 localhost: 3000上运行。

Prisma 设置

首先,运行以下命令来获得一个基本的 Prisma 设置:

Shell

npx prisma init

 

上面的命令将创建一个包含 schema.Prisma 文件的 Prisma 目录。这是您的主 Prisma 配置文件,它将包含您的数据库模式。还有。Env 文件将被添加到项目的根目录中。打开。Env 文件,并用 PostgreSQL 数据库的连接 URL 替换虚拟连接 URL。

将 prisma/schema.prisma 文件中的代码替换为以下内容:

Properties files 属性文件

datasource db {
	url = env("DATABASE_URL")
	provider="postgresql"
}
generator client {
	provider = "prisma-client-js"
}
model User {
	id        Int      @id @default(autoincrement())
	createdAt DateTime @default(now())
	email     String   @unique
	name      String
	password  String
	role      Role     @default(EMPLOYEE)
	attendance     Attendance[]
	AttendanceSheet AttendanceSheet[]
}
model AttendanceSheet {
	id        Int      @id @default(autoincrement())
	createdAt DateTime @default(now())
	updatedAt DateTime @updatedAt
	createdBy    User?    @relation(fields: [userId], references: [id])
	userId  Int?
}
model Attendance {
	id        Int      @id @default(autoincrement())
	createdAt DateTime @default(now())
	updatedAt DateTime @updatedAt
	signIn    Boolean @default(true)
	signOut   Boolean
	signInTime    DateTime @default(now())
	signOutTime   DateTime
	user    User?    @relation(fields: [userId], references: [id])
	userId  Int?
}
enum Role {
	EMPLOYEE
	ADMIN
}

 

在上面的代码片段中,我们创建了 User、 AttendanceSheet 和 Attendance Model,定义了每个模型之间的关系。

接下来,在数据库中创建这些表:

Shell

npx prisma db push

 

在运行上面的命令之后,您应该可以在终端中看到如下截图所示的输出:

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

 

创建实用程序函数

完成 Prisma 设置后,让我们创建三个实用函数,它们将在我们的应用程序中不时使用。

打开 lib/parseBody.js 文件并添加以下代码片段:

JAVAScript

export const parseBody = (body) => {
	if (typeof body === "string") return JSON.parse(body)
	return body
}

 

打开 lib/request.js 文件并添加以下代码片段。

JavaScript

export const sessionCookie = () => {
	return ({
		cookieName: "auth",
		password: process.env.SESSION_PASSWORD,
		// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
		cookieOptions: {
			secure: process.env.NODE_ENV === "production",
		},
	})
}

 

打开/lib/request.js 文件并添加以下代码片段。此函数返回铁会话铁会话的会话属性对象。

JavaScript

export const sessionCookie = () => {
	return ({
		cookieName: "auth",
		password: process.env.SESSION_PASSWORD,
		// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
		cookieOptions: {
			secure: process.env.NODE_ENV === "production",
		},
	})
}

 

接下来,将 SESSION_PASSWORD 添加到. env 文件: 它应该是一个至少有32个字符的字符串。

应用程序的样式

完成我们的实用程序功能后,让我们给应用程序添加一些样式。我们正在为这个应用程序使用 css 模块,所以打开 style/Home.modules.CSS 文件并添加以下代码片段:

CSS

.container {
	padding: 0 2rem;
}
.man {
	min-height: 100vh;
	padding: 4rem 0;
	flex: 1;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
}
.login {
	width: 450px;
}
.login input {
	width: 100%;
	height: 50px;
	margin: 4px;
}
.login button {
	width: 100%;
	height: 50px;
	margin: 4px;
}
.dashboard {
	display: grid;
	grid-template-columns: 3fr 9fr;
	grid-template-rows: 1fr;
	grid-column-gap: 0px;
	grid-row-gap: 0px;
	height: calc(100vh - 60px);
}
.navbar {
	height: 60px;
	background-color: black;
}

 

创建侧边栏组件

 

设计完成后,让我们创建侧边栏组件来帮助我们在应用程序仪表板上导航到不同的页面。打开 Component/SideBar.js 文件,并粘贴下面的代码片段。

JavaScript

import Link from 'next/link'
import { useRouter } from 'next/router'
import styles from '../styles/SideBar.module.css'

const SideBar = () => {

    const router = useRouter()

    const logout = async () => {

        try {

            const response = await fetch('/api/logout', {
                method: 'GET', 
                credentials: 'same-origin', 
            });

            if(response.status === 200)  router.push('/')

        } catch (e) {
            alert(e)
        }
  
    }
      

    return (
        <nav className={styles.sidebar}>

            <ul>

                <li> <Link href="/dashboard"> Dashboard</Link> </li>

                <li> <Link href="/dashboard/attendance"> Attendance </Link> </li>

                <li> <Link href="/dashboard/attendance-sheet"> Attendance Sheet </Link> </li>

                <li onClick={logout}> Logout </li>

            </ul>

        </nav>
    )

}

export default SideBar

登入网页

现在打开 page/index.js 文件,删除其中的所有代码,并添加以下代码段。下面的代码通过表单向 localhost: 3000/api/login 路由发送包含电子邮件和密码的发送请求。一旦验证了凭据,它就调用 router.push (’/dashboard’)方法将用户重定向到 localhost: 3000/api/dashboard:

JavaScript

import Head from 'next/head'
import { postData } from '../lib/request';
import styles from '../styles/Home.module.css'
import { useState } from 'react';
import { useRouter } from 'next/router'

export default function Home({posts}) {

  const [data, setData] = useState({email: null, password: null});

  const router = useRouter()

  const submit = (e) => {
    e.preventDefault()

    if(data.email && data.password) {
      postData('/api/login', data).then(data => {
        console.log(data); 

        if (data.status === "success") router.push('/dashboard')
      
      });
    }

  }

  return (
    <div className={styles.container}>
      <Head>
        <title>Login</title>
        <meta name="description" content="Login" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>

        <form  className={styles.login}>

          <input 
            type={"text"} 
            placeholder="Enter Your Email" 
            onChange={(e) => setData({...data, email: e.target.value})} />

          <input 
            type={"password"}  
            placeholder="Enter Your Password"
            onChange={(e) => setData({...data, password: e.target.value})} />

          <button onClick={submit}>Login</button>

        </form>
        
      </main>

    </div>
  )
}

 

设置登录 API 路由

现在打开页面/api/login.js 文件并添加以下代码片段。我们将使用 Prismaclient 进行数据库查询,IronSessionApiRoute 是用于处理 RESTful 应用程序中的用户会话的铁会话函数。

此路由处理对 localhost: 3000/api/login 的登录 POST 请求,并在用户通过身份验证后生成身份验证 Cookie。

JavaScript

import { PrismaClient } from '@prisma/client'
import { withIronSessionApiRoute } from "iron-session/next";
import { parseBody } from '../../lib/parseBody';
import { sessionCookie } from '../../lib/session';

export default withIronSessionApiRoute(
    async function loginRoute(req, res) {

      const { email, password } = parseBody(req.body)

      const prisma = new PrismaClient()

      // By unique identifier
      const user = await prisma.user.findUnique({
        where: {
        email
      },})

      if(user.password === password) {

        // get user from database then:
        user.password = undefined
        req.session.user = user
        await req.session.save();

        return res.send({ status: 'success', data: user });

      };

    res.send({ status: 'error', message: "incorrect email or password" });

  },
  sessionCookie(),
);

设置注销 API 路由

打开/page/api/logout 文件并添加下面的代码段。此路由处理对 localhost 的 GET 请求: 3000/api/logout,该请求通过销毁会话 cookie 将用户注销。

JavaScript

import { withIronSessionApiRoute } from "iron-session/next";
import { sessionCookie } from "../../lib/session";

export default withIronSessionApiRoute(
  function logoutRoute(req, res, session) {
    req.session.destroy();
    res.send({ status: "success" });
  },
  sessionCookie()
);

创建仪表板页

此页面为用户提供了登录和退出考勤表的界面。管理员还可以创建考勤表。打开 page/dashboard/index.js 文件并添加下面的代码片段。

JavaScript

import { withIronSessionSsr } from "iron-session/next";
import Head from 'next/head'
import { useState, useCallback } from "react";
import { PrismaClient } from '@prisma/client'
import SideBar from '../../components/SideBar'
import styles from '../../styles/Home.module.css'
import dashboard from '../../styles/Dashboard.module.css'
import { sessionCookie } from "../../lib/session";
import { postData } from "../../lib/request";

export default function Page(props) {

  const [attendanceSheet, setState] = useState(JSON.parse(props.attendanceSheet));

  const sign = useCallback((action="") => {

    const body = {
      attendanceSheetId: attendanceSheet[0]?.id,
      action
    }

    postData("/api/sign-attendance", body).then(data => {

      if (data.status === "success") {

        setState(prevState => {

          const newState = [...prevState]

          newState[0].attendance[0] = data.data

          return newState

        })
     
      }

    })

  }, [attendanceSheet])

  const createAttendance = useCallback(() => {

    postData("/api/create-attendance").then(data => {

      if (data.status === "success") {
        alert("New Attendance Sheet Created")
        setState([{...data.data, attendance:[]}])
      }

    })

  }, [])

  return (
    <div>

      <Head>
        <title>Attendance Management Dashboard</title>
        <meta name="description" content="dashboard" />
      </Head>

      <div className={styles.navbar}></div>

      <main className={styles.dashboard}>

        <SideBar />

        <div className={dashboard.users}>

          {
            props.isAdmin && <button className={dashboard.create} onClick={createAttendance}>Create Attendance Sheet</button>
          }
            
          { attendanceSheet.length > 0 &&

            <table className={dashboard.table}>
              <thead>
                <tr> 
                  <th>Id</th> <th>Created At</th> <th>Sign In</th> <th>Sign Out</th> 
                </tr>
              </thead>

              <tbody>
                <tr>
                  <td>{attendanceSheet[0]?.id}</td>
                  <td>{attendanceSheet[0]?.createdAt}</td>

                  {
                    attendanceSheet[0]?.attendance.length != 0 ? 
                      <>
                        <td>{attendanceSheet[0]?.attendance[0]?.signInTime}</td>
                        <td>{
                          attendanceSheet[0]?.attendance[0]?.signOut ? 
                          attendanceSheet[0]?.attendance[0]?.signOutTime: <button onClick={() => sign("sign-out")}> Sign Out </button> }</td>
                      </>
                      :
                      <>
                        <td> <button onClick={() => sign()}> Sign In </button> </td>
                        <td>{""}</td>
                      </>
                  }
                </tr>
              </tbody>

            </table>

          }
          
        </div>

      </main>

    </div>
  )
}

 

我们使用 getServerSideProps 来生成页面数据,而 IronSessionSsr 是用于处理服务器端呈现页面的 Iron-session 函数。在下面的代码片段中,我们使用考勤表中的一行查询考勤表的最后一行,其中 userId 等于存储在用户会话上的 User id。我们还检查用户是否是 ADMIN。

JavaScript

export const getServerSideProps = withIronSessionSsr( async ({req}) => {

  const user = req.session.user

  const prisma = new PrismaClient()

  const attendanceSheet = await prisma.attendanceSheet.findMany({  
    take: 1,
    orderBy: {
      id: 'desc',
    },
    include: { 
      attendance: {
        where: {
          userId: user.id
        },
      }
    }
  })

  return {
    props: {
      attendanceSheet: JSON.stringify(attendanceSheet),
      isAdmin: user.role === "ADMIN"
    }
  }

}, sessionCookie())

设置创建出勤 API 路由

打开页面/api/create-around. js 文件并添加下面的代码片段。

JavaScript

import { PrismaClient } from '@prisma/client'
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionCookie } from '../../lib/session';

  
export default withIronSessionApiRoute( async function handler(req, res) {

    const prisma = new PrismaClient()

    const user = req.session.user

    const attendanceShe

设置签到 API 路由

此路由处理我们对 localhost 的 API POST 请求:
3000/API/sign-publications。该路由接受 POST 请求,而 attanceSheetId 和 action 用于登录和退出 attancesheet。

打开/page/api/sign-around. js 文件并添加下面的代码片段。

JavaScript

import { PrismaClient } from '@prisma/client'
import { withIronSessionApiRoute } from "iron-session/next";
import { parseBody } from '../../lib/parseBody';
import { sessionCookie } from '../../lib/session';

  
export default withIronSessionApiRoute( async function handler(req, res) {

    const prisma = new PrismaClient()

    const {attendanceSheetId, action} = parseBody(req.body)

    const user = req.session.user

    const attendance = await prisma.attendance.findMany({
        where: {
            userId: user.id,
            attendanceSheetId: attendanceSheetId
        }
    })

    //check if atendance have been created
    if (attendance.length === 0) {
        const attendance = await prisma.attendance.create({
            data: {
                userId: user.id,
                attendanceSheetId: attendanceSheetId,
                signIn: true,
                signOut: false,
                signOutTime: new Date()
            },
        })   

        return res.json({status: "success", data: attendance});

    } else if (action === "sign-out") {
        await prisma.attendance.updateMany({
            where: {
                userId: user.id,
                attendanceSheetId: attendanceSheetId
            },
            data: {
              signOut: true,
              signOutTime: new Date()
            },
        })

        return res.json({status: "success", data: { ...attendance[0], signOut: true, signOutTime: new Date()}});
    }

    res.json({status: "success", data: attendance});
    
}, sessionCookie())

创建出勤页面

此服务器端呈现的页面显示了登录用户的所有出勤表。打开
/page/dashboard/attance.js 文件并添加下面的代码片段。

JavaScript

import { withIronSessionSsr } from "iron-session/next";
import Head from 'next/head'
import { PrismaClient } from '@prisma/client'
import SideBar from '../../components/SideBar'
import styles from '../../styles/Home.module.css'
import dashboard from '../../styles/Dashboard.module.css'
import { sessionCookie } from "../../lib/session";

export default function Page(props) {

  const data = JSON.parse(props.attendanceSheet)

  return (
    <div>

      <Head>
        <title>Attendance Management Dashboard</title>
        <meta name="description" content="dashboard" />
      </Head>

      <div className={styles.navbar}></div>

      <main className={styles.dashboard}>

        <SideBar />

        <div className={dashboard.users}>

        <table className={dashboard.table}>

          <thead>

            <tr> 
              <th> Attendance Id</th> <th>Date</th> 
              <th>Sign In Time</th> <th>Sign Out Time</th> 
            </tr> 

          </thead>

            <tbody>

              {
                data.map(data =>   {

                  const {id, createdAt, attendance } = data

  
                  return (
                    <tr key={id}> 

                      <td>{id}</td> <td>{createdAt}</td>  

                      { attendance.length === 0 ? 
                      
                        (
                          <>
                            <td>You did not Sign In</td>
                            <td>You did not Sign Out</td>
                          </>
                        )
                        :
                        (
                          <>
                            <td>{attendance[0]?.signInTime}</td>
                            <td>{attendance[0]?.signOut ? attendance[0]?.signOutTime : "You did not Sign Out"}</td>
                          </>
                        )
                        
                      }
              
                    </tr>
                  )

                })

              }  

            </tbody>

          </table>

        </div>

      </main>

    </div>
  )
}
In the code snippet below, we query for all the rows from the attendanceSheet table and also fetch the attendance where the userId is equal to the user id stored in the user session.

export const getServerSideProps = withIronSessionSsr( async ({req}) => {

  const user = req.session.user

  const prisma = new PrismaClient()
  
  const attendanceSheet = await prisma.attendanceSheet.findMany({
    orderBy: {
      id: 'desc',
    },
    include: { 
      attendance: {
        where: {
          userId: user.id
        },
      }
    }
  })

  return {
    props: {
      attendanceSheet: JSON.stringify(attendanceSheet),
    }
  }

}, sessionCookie())

创建出勤单页

这个服务器端呈现的页面显示了所有的考勤表以及在该考勤表上签名的员工。打开
/page/dashboard/attance.js 文件并添加下面的代码片段。

JavaScript

import { withIronSessionSsr } from "iron-session/next";
import Head from 'next/head'
import { PrismaClient } from '@prisma/client'
import SideBar from '../../components/SideBar'
import styles from '../../styles/Home.module.css'
import dashboard from '../../styles/Dashboard.module.css'
import { sessionCookie } from "../../lib/session";

export default function Page(props) {

  const data = JSON.parse(props.attendanceSheet)

  return (
    <div>

      <Head>
        <title>Attendance Management Dashboard</title>
        <meta name="description" content="dashboard" />
      </Head>

      <div className={styles.navbar}></div>

      <main className={styles.dashboard}>

        <SideBar />

        <div className={dashboard.users}>

        {
          data?.map(data => {

            const {id, createdAt, attendance } = data

            return (
              <>

                <table key={data.id} className={dashboard.table}>

                  <thead>
                    
                    <tr> 
                      <th> Attendance Id</th> <th>Date</th> 
                      <th> Name </th> <th> Email </th> <th> Role </th>
                      <th>Sign In Time</th> <th>Sign Out Time</th> 
                    </tr> 

                  </thead>

                  <tbody>

                    {
                      (attendance.length === 0)  &&
                      (
                        <>
                        <tr><td> {id} </td> <td>{createdAt}</td> <td colSpan={5}> No User signed this sheet</td></tr>
                        </>
                      )
                    }

                    {
                      attendance.map(data => {

                        const {name, email, role} = data.user

                      
                        return (
                          <tr key={id}> 

                            <td>{id}</td> <td>{createdAt}</td>  

                            <td>{name}</td> <td>{email}</td>

                            <td>{role}</td>

                            <td>{data.signInTime}</td>

                            <td>{data.signOut ? attendance[0]?.signOutTime: "User did not Sign Out"}</td>  
                    
                          </tr>
                        )

                      })

                    }  

                  </tbody>
                  
                </table>
              </>
            )
          })
          
          }

        </div>

      </main>

    </div>
  )
}

 

在下面的代码片段中,我们将查询 attancesheet 表中的所有行,并通过选择姓名、电子邮件和角色来获取出席率。

JavaScript

export const getServerSideProps = withIronSessionSsr(async () => {

  const prisma = new PrismaClient()

  const attendanceSheet = await prisma.attendanceSheet.findMany({
    orderBy: {
      id: 'desc',
    },
    include: { 
      attendance: {
        include: { 
          user: {
            select: {
              name: true, 
              email: true, 
              role: true
            }
          }
        }
      },
    },

  })

  return {
    props: {
      attendanceSheet: JSON.stringify(attendanceSheet),
    }
  }

}, sessionCookie())

测试应用程序

首先,我们必须将用户添加到数据库中。我们要用Prisma做这个。要启动 Prisma ,请运行以下命令:

Shell

npx prisma studio

 

Prisma 索引页面如下:

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

 

要创建一个具有 ADMIN 角色的数据库用户和多个具有 EMPLOYEE 角色的用户,请访问以下页面:

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

 

单击 Add record,然后填写所需的字段: password、 name、 email 和 role。完成后,单击绿色的 Save 1 change 按钮。注意,为了简单起见,我们没有散列密码。

 

用yarn dev启动服务器。这将启动服务器并在[ localhost: 3000](< http://localhost:3000)上运行应用程序,登录页面如下所示。

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

 

使用具有 ADMIN 角色的用户登录,因为只有管理用户可以创建考勤表。一旦登录成功,应用程序将重定向到您的仪表板。

单击 CreateAtnatureShet 按钮创建考勤表,然后等待请求完成,考勤表将出现。用户仪表板如下所示。

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

 

出勤表如下所示,单击“登录”按钮即可登录。登录成功后,将显示登录时间,并且可以看到 Sign Out 按钮。单击“登出”按钮登出,并与不同的用户多次重复此过程。

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

 

接下来点击侧边栏中的出席链接,查看用户的出席情况。结果应与下列结果相符:

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

 

接下来点击侧边栏上的出勤表链接,查看所有用户的出勤情况。结果如下:

如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序

 

结论

在本文中,您学习了如何在 Next.js 中使用自定义 Fastify 服务器。您还了解了 Prisma 和 Prisma 工作室。我已经向您介绍了如何将 Prisma 连接到 Postgres 数据库,以及如何使用 Prisma 客户端和 Prisma studio创建、读取和更新数据库。

您还学习了如何使用铁会话对用户进行身份验证。在本教程中,我们构建了一个完整的应用程序,它使用 Next.js、 Prisma、 Postgres 和 Fastify 管理员工出勤率。请继续收听,下次再见。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>