Minh  Nguyet

Minh Nguyet

1657301820

Cách Xây Dựng ứng Dụng Fullstack Với Next.js, Prisma, Postgres

Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng ứng dụng Full-stack bằng Next.js, Prisma, Postgres và Fastify. Chúng tôi sẽ xây dựng một ứng dụng demo quản lý chấm công để quản lý việc chấm công của nhân viên. Quy trình của ứng dụng rất đơn giản: người dùng quản trị đăng nhập, tạo bảng chấm công trong ngày, sau đó mọi nhân viên đăng nhập và đăng xuất khỏi bảng chấm công.

Next.js là gì?

Next.js là một khung công tác React linh hoạt cung cấp cho bạn các khối xây dựng để tạo các ứng dụng web nhanh chóng. Nó thường được gọi là fullstack React framework vì nó cho phép có cả ứng dụng frontend và backend trên cùng một codebase hoạt động như vậy với các chức năng serverless.

Prisma là gì?

Prisma là một ORM mã nguồn mở, Node.js và Typescript giúp đơn giản hóa đáng kể mô hình dữ liệu, di chuyển và truy cập dữ liệu cho cơ sở dữ liệu SQL. Tại thời điểm viết bài này, Prisma hỗ trợ các hệ quản trị cơ sở dữ liệu sau: PostgreSQL, MySQL, MariaDB, SQLite, AWS Aurora, Microsoft SQL Server, Azure SQL và MongoDB. Bạn cũng có thể muốn nhấp vào đây để xem danh sách tất cả các hệ thống quản lý cơ sở dữ liệu được hỗ trợ.

Postgres là gì?

Postgres còn được gọi là PostgreSQL và nó là một hệ quản trị cơ sở dữ liệu quan hệ mã nguồn mở và miễn phí. Nó là một tập hợp siêu ngôn ngữ SQL và nó có nhiều tính năng cho phép các nhà phát triển lưu trữ và chia tỷ lệ khối lượng công việc dữ liệu phức tạp một cách an toàn.

Điều kiện tiên quyết

Hướng dẫn này là một hướng dẫn trình diễn thực hành. Do đó, tốt nhất là bạn đã cài đặt những thứ sau trên máy tính của mình để làm theo:

Mã cho hướng dẫn này có sẵn ở đây trên Github, vì vậy hãy sao chép nó và làm theo.

Thiết lập dự án

Hãy bắt đầu bằng cách thiết lập ứng dụng Next.js của chúng tôi. Để bắt đầu, hãy chạy lệnh bên dưới.

npx create-next-app@latest

Chờ cài đặt hoàn tất, sau đó chạy lệnh bên dưới để cài đặt các phụ thuộc của chúng tôi.

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

Chờ cho quá trình cài đặt hoàn tất.

Thiết lập Next.js và Fastify

Theo mặc định, Next.js không sử dụng Fastify làm máy chủ của nó. Để sử dụng Fastify để phân phối ứng dụng Next.js của chúng tôi, hãy chỉnh sửa trường script trong package.jsontệp bằng đoạn mã bên dưới.

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

Tạo máy chủ Fastify của chúng tôi

Bây giờ chúng ta hãy tạo một server.jstệp. Tệp này là điểm nhập của ứng dụng của chúng tôi và sau đó chúng tôi thêm tệp require('fastify-nextjs')để bao gồm plugin hiển thị API Next.js trong fastify để xử lý kết xuất.

Mở server.jstệp và thêm các đoạn mã bên dưới:

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>')
})

Trong đoạn mã trên, chúng tôi sử dụng fastify-nextjsplugin hiển thị API Next.js trong Fastify để xử lý kết xuất cho chúng tôi. Sau đó, chúng tôi phân tích cú pháp các yêu cầu đến bằng noOpParserhàm làm cho phần thân yêu cầu có sẵn cho các trình xử lý tuyến API Next.js của chúng tôi và chúng tôi xác định hai tuyến cho ứng dụng của mình bằng [fastify.next](<http://fastify.next>lệnh. Sau đó, chúng tôi tạo máy chủ Fastify của mình và làm cho nó lắng nghe cổng 3000.

Bây giờ, hãy tiếp tục và chạy ứng dụng bằng yarn devlệnh: ứng dụng sẽ được chạy localhost:3000.

Thiết lập Prisma

Đầu tiên, hãy chạy lệnh sau để thiết lập Prisma cơ bản:

npx prisma init

Lệnh trên sẽ tạo một thư mục prima với một schema.prismatập tin. Đây là tệp cấu hình Prisma chính của bạn, tệp này sẽ chứa lược đồ cơ sở dữ liệu của bạn. Ngoài ra, một .envtệp sẽ được thêm vào thư mục gốc của dự án. Mở .envtệp và thay thế URL kết nối giả bằng URL kết nối của cơ sở dữ liệu PostgreSQL của bạn.

Thay thế mã trong prisma/schema.prismatệp bằng mã sau:

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
}

Trong đoạn mã trên, chúng tôi đã tạo Người dùng, Bảng điểm và Mô hình chấm công, xác định mối quan hệ giữa mỗi mô hình.

Tiếp theo, tạo các bảng này trong cơ sở dữ liệu. Chạy lệnh sau:

npx prisma db push

Sau khi chạy lệnh trên, bạn sẽ thấy đầu ra như được hiển thị trong ảnh chụp màn hình bên dưới trong thiết bị đầu cuối của bạn:

Tạo các chức năng tiện ích

Sau khi thiết lập xong Prisma, chúng ta hãy tạo ba chức năng tiện ích sẽ được sử dụng trong ứng dụng của chúng tôi theo thời gian.

Mở tệp lib / parseBody.js và thêm đoạn mã sau. Hàm này phân tích cú pháp phần thân yêu cầu thành JSON:

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

Mở tệp lib / request.js và thêm đoạn mã sau. Chức năng này gửi một yêu cầu ĐĂNG.

export async function postData(url = '', data='') {
const response = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify(data)
});
return response.json();
}

Mở tệp /lib/request.js và thêm đoạn mã sau. Hàm này trả về một đối tượng thuộc tính phiên cho phiên sắt-session sắt.

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",
},
})
}

Tiếp theo, thêm SESSION_PASSWORDvào tệp .env: nó phải là một chuỗi có ít nhất 32 ký tự.

Tạo kiểu cho ứng dụng

Với các chức năng Tiện ích của chúng tôi đã hoàn thành, hãy thêm một số kiểu vào ứng dụng. Chúng tôi đang sử dụng mô-đun css cho ứng dụng này, vì vậy hãy mở styles/Home.modules.csstệp và thêm đoạn mã bên dưới:

.container {
  padding: 0 2rem;
}

.main {
  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;
}

Tạo thành phần thanh bên

Với việc tạo kiểu xong, hãy tạo thành phần thanh bên để giúp chúng tôi điều hướng đến các trang khác nhau trên trang tổng quan ứng dụng của chúng tôi. Mở tệp thành phần / SideBar.js và dán đoạn mã bên dưới.

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

Trang đăng nhập

Bây giờ, hãy mở tệp page / index.js, xóa tất cả mã ở đó và thêm đoạn mã sau. Đoạn mã dưới đây sẽ gửi một yêu cầu đăng bài với email và mật khẩu được cung cấp qua biểu mẫu tới localhost: 3000 / api / đường đăng nhập. Khi thông tin xác thực được xác thực, nó sẽ gọi router.push('/dashboard')phương thức chuyển hướng người dùng đến localhost: 3000 / api / dashboard:

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>
  )
}

Thiết lập tuyến API đăng nhập

Bây giờ, hãy mở tệp trang / api / login.js và thêm đoạn mã sau. Chúng tôi sẽ sử dụng PrismaClientđể thực hiện các truy vấn cơ sở dữ liệu của mình và withIronSessionApiRoutelà hàm iron-session để xử lý các phiên của người dùng trong các ứng dụng RESTful.

Lộ trình này xử lý yêu cầu POST đăng nhập đối với localhost: 3000 / api / login và tạo cookie xác thực sau khi người dùng được xác thực.

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(),
);

Thiết lập tuyến API đăng xuất

Mở tệp / page / api / logout và thêm đoạn mã bên dưới. Tuyến này xử lý các yêu cầu GET tới localhost: 3000 / api / logout đăng xuất người dùng bằng cách hủy cookie phiên.

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()
);

Tạo Trang Bảng điều khiển

Trang này cung cấp giao diện để người dùng đăng nhập và đăng xuất khỏi bảng điểm danh. Quản trị viên cũng có thể tạo một bảng điểm danh. Mở tệp trang / dashboard / index.js và thêm đoạn mã bên dưới.

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>
  )
}

Chúng tôi sử dụng getServerSidePropsđể tạo dữ liệu trang và withIronSessionSsrlà hàm phiên sắt để làm việc với các trang được hiển thị phía máy chủ. Trong đoạn mã sau, chúng tôi truy vấn hàng cuối cùng của bảng tham dự với một hàng từ bảng tham dự, trong đó giá trị userIdbằng với Id người dùng được lưu trữ trên phiên người dùng. Chúng tôi cũng kiểm tra xem người dùng có phải là QUẢN TRỊ VIÊN không.

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())

Thiết lập lộ trình tạo API chuyên cần

Mở tệp page / api / create-secure.js và thêm đoạn mã bên dưới.

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 attendanceSheet = await prisma.attendanceSheet.create({
        data: {
          userId: user.id,
        },
    })

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

Thiết lập lộ trình API chấm công đăng ký

Tuyến đường này xử lý yêu cầu API POST của chúng tôi tới localhost: 3000 / api / sign -casting. Tuyến chấp nhận yêu cầu POST, trong khi attendanceSheetIdactionđược sử dụng để đăng nhập và đăng xuất attendanceSheet.

Mở tệp /page/api/sign-attendance.js và thêm đoạn mã bên dưới.

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())

Tạo trang điểm danh

Trang kết xuất phía máy chủ này hiển thị tất cả các trang tham dự cho một người dùng đã đăng nhập. Mở tệp /page/dashboard/attendance.js và thêm đoạn mã bên dưới.

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>
  )
}

Trong đoạn mã bên dưới, chúng tôi truy vấn tất cả các hàng từ attendanceSheetbảng và cũng tìm nạp lượt tham dự userIdbằng với id người dùng được lưu trữ trong phiên người dùng.

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())

Tạo trang bảng điểm danh

Trang kết xuất phía máy chủ này hiển thị tất cả các phiếu chấm công và Nhân viên đã đăng nhập vào bảng chấm công đó. Mở tệp /page/dashboard/attendance.js và thêm đoạn mã bên dưới.

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>
  )
}

Trong đoạn mã bên dưới, chúng tôi truy vấn tất cả các hàng từ các attendanceSheetbảng và cũng tìm nạp thông tin tham dự bằng cách chọn tên, email và vai trò.

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())

Thử nghiệm ứng dụng

Đầu tiên, chúng ta phải thêm Người dùng vào cơ sở dữ liệu của mình. Chúng tôi sẽ làm điều này với Prisma Studio. Để khởi động Prisma studio, hãy chạy lệnh dưới đây:

npx prisma studio

Trang chỉ mục Prisma trông như thế này:

Trang chỉ mục Prisma


Để tạo người dùng cơ sở dữ liệu có vai trò QUẢN TRỊ và nhiều người dùng có vai trò NHÂN VIÊN, hãy truy cập trang này:

Thêm người dùng

Nhấp vào Thêm bản ghi, sau đó điền vào các trường bắt buộc: mật khẩu, tên, email và vai trò. Khi bạn đã hoàn tất, hãy nhấp vào nút Lưu 1 thay đổi màu xanh lục. Lưu ý rằng để đơn giản, chúng tôi đã không băm mật khẩu.

Khởi động máy chủ với yarn dev. Thao tác này khởi động máy chủ và chạy ứng dụng trên [localhost: 3000] (<http: // localhost: 3000>), trang đăng nhập được hiển thị bên dưới.

Trang đăng nhập

Đăng nhập bằng người dùng có vai trò QUẢN TRỊ vì chỉ người dùng quản trị mới có thể tạo bảng điểm danh. Sau khi đăng nhập thành công, ứng dụng sẽ chuyển hướng bạn đến trang tổng quan của bạn.

Bấm vào nút Tạo Bảng điểm danh để tạo bảng điểm danh, sau đó đợi yêu cầu kết thúc thì bảng điểm danh sẽ hiện ra. Bảng điều khiển người dùng được hiển thị bên dưới.

Tạo Bảng điểm danh

Bảng điểm danh được hiển thị bên dưới, nhấp vào nút Sign In để đăng nhập. Sau khi đăng nhập thành công, thời gian đăng nhập sẽ được hiển thị và nút Sign Out sẽ hiển thị. Nhấp vào nút Sign Out để đăng xuất và lặp lại quá trình này nhiều lần với những người dùng khác nhau.

Đăng nhập thành công

Tiếp theo, nhấp vào liên kết Điểm danh trong thanh bên để xem điểm danh của người dùng. Các kết quả phải khớp với những kết quả được hiển thị bên dưới:

Trang điểm danh

Tiếp theo nhấp vào liên kết Bảng điểm danh trên thanh bên để xem người dùng tham dự tất cả người dùng. Kết quả được hiển thị bên dưới:

Trang bảng điểm danh

Sự kết luận

Trong bài viết này, bạn đã học cách sử dụng máy chủ Fastify tùy chỉnh với Next.js. Bạn cũng đã tìm hiểu về Prisma và Prisma studio. Tôi đã hướng dẫn bạn cách kết nối Prisma với cơ sở dữ liệu Postgres và cách tạo, đọc và cập nhật cơ sở dữ liệu bằng ứng dụng khách Prisma và studio Prisma.

Liên kết: https://arctype.com/blog/fullstack-nextjs-postgres-fastify/

#fullstack #nextjs #postgre #fastify

What is GEEK

Buddha Community

Cách Xây Dựng ứng Dụng Fullstack Với Next.js, Prisma, Postgres

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript

Minh  Nguyet

Minh Nguyet

1657301820

Cách Xây Dựng ứng Dụng Fullstack Với Next.js, Prisma, Postgres

Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng ứng dụng Full-stack bằng Next.js, Prisma, Postgres và Fastify. Chúng tôi sẽ xây dựng một ứng dụng demo quản lý chấm công để quản lý việc chấm công của nhân viên. Quy trình của ứng dụng rất đơn giản: người dùng quản trị đăng nhập, tạo bảng chấm công trong ngày, sau đó mọi nhân viên đăng nhập và đăng xuất khỏi bảng chấm công.

Next.js là gì?

Next.js là một khung công tác React linh hoạt cung cấp cho bạn các khối xây dựng để tạo các ứng dụng web nhanh chóng. Nó thường được gọi là fullstack React framework vì nó cho phép có cả ứng dụng frontend và backend trên cùng một codebase hoạt động như vậy với các chức năng serverless.

Prisma là gì?

Prisma là một ORM mã nguồn mở, Node.js và Typescript giúp đơn giản hóa đáng kể mô hình dữ liệu, di chuyển và truy cập dữ liệu cho cơ sở dữ liệu SQL. Tại thời điểm viết bài này, Prisma hỗ trợ các hệ quản trị cơ sở dữ liệu sau: PostgreSQL, MySQL, MariaDB, SQLite, AWS Aurora, Microsoft SQL Server, Azure SQL và MongoDB. Bạn cũng có thể muốn nhấp vào đây để xem danh sách tất cả các hệ thống quản lý cơ sở dữ liệu được hỗ trợ.

Postgres là gì?

Postgres còn được gọi là PostgreSQL và nó là một hệ quản trị cơ sở dữ liệu quan hệ mã nguồn mở và miễn phí. Nó là một tập hợp siêu ngôn ngữ SQL và nó có nhiều tính năng cho phép các nhà phát triển lưu trữ và chia tỷ lệ khối lượng công việc dữ liệu phức tạp một cách an toàn.

Điều kiện tiên quyết

Hướng dẫn này là một hướng dẫn trình diễn thực hành. Do đó, tốt nhất là bạn đã cài đặt những thứ sau trên máy tính của mình để làm theo:

Mã cho hướng dẫn này có sẵn ở đây trên Github, vì vậy hãy sao chép nó và làm theo.

Thiết lập dự án

Hãy bắt đầu bằng cách thiết lập ứng dụng Next.js của chúng tôi. Để bắt đầu, hãy chạy lệnh bên dưới.

npx create-next-app@latest

Chờ cài đặt hoàn tất, sau đó chạy lệnh bên dưới để cài đặt các phụ thuộc của chúng tôi.

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

Chờ cho quá trình cài đặt hoàn tất.

Thiết lập Next.js và Fastify

Theo mặc định, Next.js không sử dụng Fastify làm máy chủ của nó. Để sử dụng Fastify để phân phối ứng dụng Next.js của chúng tôi, hãy chỉnh sửa trường script trong package.jsontệp bằng đoạn mã bên dưới.

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

Tạo máy chủ Fastify của chúng tôi

Bây giờ chúng ta hãy tạo một server.jstệp. Tệp này là điểm nhập của ứng dụng của chúng tôi và sau đó chúng tôi thêm tệp require('fastify-nextjs')để bao gồm plugin hiển thị API Next.js trong fastify để xử lý kết xuất.

Mở server.jstệp và thêm các đoạn mã bên dưới:

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>')
})

Trong đoạn mã trên, chúng tôi sử dụng fastify-nextjsplugin hiển thị API Next.js trong Fastify để xử lý kết xuất cho chúng tôi. Sau đó, chúng tôi phân tích cú pháp các yêu cầu đến bằng noOpParserhàm làm cho phần thân yêu cầu có sẵn cho các trình xử lý tuyến API Next.js của chúng tôi và chúng tôi xác định hai tuyến cho ứng dụng của mình bằng [fastify.next](<http://fastify.next>lệnh. Sau đó, chúng tôi tạo máy chủ Fastify của mình và làm cho nó lắng nghe cổng 3000.

Bây giờ, hãy tiếp tục và chạy ứng dụng bằng yarn devlệnh: ứng dụng sẽ được chạy localhost:3000.

Thiết lập Prisma

Đầu tiên, hãy chạy lệnh sau để thiết lập Prisma cơ bản:

npx prisma init

Lệnh trên sẽ tạo một thư mục prima với một schema.prismatập tin. Đây là tệp cấu hình Prisma chính của bạn, tệp này sẽ chứa lược đồ cơ sở dữ liệu của bạn. Ngoài ra, một .envtệp sẽ được thêm vào thư mục gốc của dự án. Mở .envtệp và thay thế URL kết nối giả bằng URL kết nối của cơ sở dữ liệu PostgreSQL của bạn.

Thay thế mã trong prisma/schema.prismatệp bằng mã sau:

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
}

Trong đoạn mã trên, chúng tôi đã tạo Người dùng, Bảng điểm và Mô hình chấm công, xác định mối quan hệ giữa mỗi mô hình.

Tiếp theo, tạo các bảng này trong cơ sở dữ liệu. Chạy lệnh sau:

npx prisma db push

Sau khi chạy lệnh trên, bạn sẽ thấy đầu ra như được hiển thị trong ảnh chụp màn hình bên dưới trong thiết bị đầu cuối của bạn:

Tạo các chức năng tiện ích

Sau khi thiết lập xong Prisma, chúng ta hãy tạo ba chức năng tiện ích sẽ được sử dụng trong ứng dụng của chúng tôi theo thời gian.

Mở tệp lib / parseBody.js và thêm đoạn mã sau. Hàm này phân tích cú pháp phần thân yêu cầu thành JSON:

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

Mở tệp lib / request.js và thêm đoạn mã sau. Chức năng này gửi một yêu cầu ĐĂNG.

export async function postData(url = '', data='') {
const response = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify(data)
});
return response.json();
}

Mở tệp /lib/request.js và thêm đoạn mã sau. Hàm này trả về một đối tượng thuộc tính phiên cho phiên sắt-session sắt.

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",
},
})
}

Tiếp theo, thêm SESSION_PASSWORDvào tệp .env: nó phải là một chuỗi có ít nhất 32 ký tự.

Tạo kiểu cho ứng dụng

Với các chức năng Tiện ích của chúng tôi đã hoàn thành, hãy thêm một số kiểu vào ứng dụng. Chúng tôi đang sử dụng mô-đun css cho ứng dụng này, vì vậy hãy mở styles/Home.modules.csstệp và thêm đoạn mã bên dưới:

.container {
  padding: 0 2rem;
}

.main {
  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;
}

Tạo thành phần thanh bên

Với việc tạo kiểu xong, hãy tạo thành phần thanh bên để giúp chúng tôi điều hướng đến các trang khác nhau trên trang tổng quan ứng dụng của chúng tôi. Mở tệp thành phần / SideBar.js và dán đoạn mã bên dưới.

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

Trang đăng nhập

Bây giờ, hãy mở tệp page / index.js, xóa tất cả mã ở đó và thêm đoạn mã sau. Đoạn mã dưới đây sẽ gửi một yêu cầu đăng bài với email và mật khẩu được cung cấp qua biểu mẫu tới localhost: 3000 / api / đường đăng nhập. Khi thông tin xác thực được xác thực, nó sẽ gọi router.push('/dashboard')phương thức chuyển hướng người dùng đến localhost: 3000 / api / dashboard:

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>
  )
}

Thiết lập tuyến API đăng nhập

Bây giờ, hãy mở tệp trang / api / login.js và thêm đoạn mã sau. Chúng tôi sẽ sử dụng PrismaClientđể thực hiện các truy vấn cơ sở dữ liệu của mình và withIronSessionApiRoutelà hàm iron-session để xử lý các phiên của người dùng trong các ứng dụng RESTful.

Lộ trình này xử lý yêu cầu POST đăng nhập đối với localhost: 3000 / api / login và tạo cookie xác thực sau khi người dùng được xác thực.

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(),
);

Thiết lập tuyến API đăng xuất

Mở tệp / page / api / logout và thêm đoạn mã bên dưới. Tuyến này xử lý các yêu cầu GET tới localhost: 3000 / api / logout đăng xuất người dùng bằng cách hủy cookie phiên.

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()
);

Tạo Trang Bảng điều khiển

Trang này cung cấp giao diện để người dùng đăng nhập và đăng xuất khỏi bảng điểm danh. Quản trị viên cũng có thể tạo một bảng điểm danh. Mở tệp trang / dashboard / index.js và thêm đoạn mã bên dưới.

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>
  )
}

Chúng tôi sử dụng getServerSidePropsđể tạo dữ liệu trang và withIronSessionSsrlà hàm phiên sắt để làm việc với các trang được hiển thị phía máy chủ. Trong đoạn mã sau, chúng tôi truy vấn hàng cuối cùng của bảng tham dự với một hàng từ bảng tham dự, trong đó giá trị userIdbằng với Id người dùng được lưu trữ trên phiên người dùng. Chúng tôi cũng kiểm tra xem người dùng có phải là QUẢN TRỊ VIÊN không.

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())

Thiết lập lộ trình tạo API chuyên cần

Mở tệp page / api / create-secure.js và thêm đoạn mã bên dưới.

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 attendanceSheet = await prisma.attendanceSheet.create({
        data: {
          userId: user.id,
        },
    })

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

Thiết lập lộ trình API chấm công đăng ký

Tuyến đường này xử lý yêu cầu API POST của chúng tôi tới localhost: 3000 / api / sign -casting. Tuyến chấp nhận yêu cầu POST, trong khi attendanceSheetIdactionđược sử dụng để đăng nhập và đăng xuất attendanceSheet.

Mở tệp /page/api/sign-attendance.js và thêm đoạn mã bên dưới.

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())

Tạo trang điểm danh

Trang kết xuất phía máy chủ này hiển thị tất cả các trang tham dự cho một người dùng đã đăng nhập. Mở tệp /page/dashboard/attendance.js và thêm đoạn mã bên dưới.

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>
  )
}

Trong đoạn mã bên dưới, chúng tôi truy vấn tất cả các hàng từ attendanceSheetbảng và cũng tìm nạp lượt tham dự userIdbằng với id người dùng được lưu trữ trong phiên người dùng.

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())

Tạo trang bảng điểm danh

Trang kết xuất phía máy chủ này hiển thị tất cả các phiếu chấm công và Nhân viên đã đăng nhập vào bảng chấm công đó. Mở tệp /page/dashboard/attendance.js và thêm đoạn mã bên dưới.

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>
  )
}

Trong đoạn mã bên dưới, chúng tôi truy vấn tất cả các hàng từ các attendanceSheetbảng và cũng tìm nạp thông tin tham dự bằng cách chọn tên, email và vai trò.

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())

Thử nghiệm ứng dụng

Đầu tiên, chúng ta phải thêm Người dùng vào cơ sở dữ liệu của mình. Chúng tôi sẽ làm điều này với Prisma Studio. Để khởi động Prisma studio, hãy chạy lệnh dưới đây:

npx prisma studio

Trang chỉ mục Prisma trông như thế này:

Trang chỉ mục Prisma


Để tạo người dùng cơ sở dữ liệu có vai trò QUẢN TRỊ và nhiều người dùng có vai trò NHÂN VIÊN, hãy truy cập trang này:

Thêm người dùng

Nhấp vào Thêm bản ghi, sau đó điền vào các trường bắt buộc: mật khẩu, tên, email và vai trò. Khi bạn đã hoàn tất, hãy nhấp vào nút Lưu 1 thay đổi màu xanh lục. Lưu ý rằng để đơn giản, chúng tôi đã không băm mật khẩu.

Khởi động máy chủ với yarn dev. Thao tác này khởi động máy chủ và chạy ứng dụng trên [localhost: 3000] (<http: // localhost: 3000>), trang đăng nhập được hiển thị bên dưới.

Trang đăng nhập

Đăng nhập bằng người dùng có vai trò QUẢN TRỊ vì chỉ người dùng quản trị mới có thể tạo bảng điểm danh. Sau khi đăng nhập thành công, ứng dụng sẽ chuyển hướng bạn đến trang tổng quan của bạn.

Bấm vào nút Tạo Bảng điểm danh để tạo bảng điểm danh, sau đó đợi yêu cầu kết thúc thì bảng điểm danh sẽ hiện ra. Bảng điều khiển người dùng được hiển thị bên dưới.

Tạo Bảng điểm danh

Bảng điểm danh được hiển thị bên dưới, nhấp vào nút Sign In để đăng nhập. Sau khi đăng nhập thành công, thời gian đăng nhập sẽ được hiển thị và nút Sign Out sẽ hiển thị. Nhấp vào nút Sign Out để đăng xuất và lặp lại quá trình này nhiều lần với những người dùng khác nhau.

Đăng nhập thành công

Tiếp theo, nhấp vào liên kết Điểm danh trong thanh bên để xem điểm danh của người dùng. Các kết quả phải khớp với những kết quả được hiển thị bên dưới:

Trang điểm danh

Tiếp theo nhấp vào liên kết Bảng điểm danh trên thanh bên để xem người dùng tham dự tất cả người dùng. Kết quả được hiển thị bên dưới:

Trang bảng điểm danh

Sự kết luận

Trong bài viết này, bạn đã học cách sử dụng máy chủ Fastify tùy chỉnh với Next.js. Bạn cũng đã tìm hiểu về Prisma và Prisma studio. Tôi đã hướng dẫn bạn cách kết nối Prisma với cơ sở dữ liệu Postgres và cách tạo, đọc và cập nhật cơ sở dữ liệu bằng ứng dụng khách Prisma và studio Prisma.

Liên kết: https://arctype.com/blog/fullstack-nextjs-postgres-fastify/

#fullstack #nextjs #postgre #fastify

Eva  Murphy

Eva Murphy

1625674200

Google analytics Setup with Next JS, React JS using Router Events - 14

In this video, we are going to implement Google Analytics to our Next JS application. Tracking page views of an application is very important.

Google analytics will allow us to track analytics information.

Frontend: https://github.com/amitavroy/video-reviews
API: https://github.com/amitavdevzone/video-review-api
App link: https://video-reviews.vercel.app

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #js #react js #react #next #google analytics

Kiera Smart

Kiera Smart

1597654680

Building a Backend for React with Next.js, Prisma 2, and Postgres

Want to persist your data between page loads? You need a backend! Becoming a full stack developer means learning the ins and outs of how to add a backend that your frontend in React can interact with. In this video we will take an existing React app using Google Maps, and using Next.js (API routes), Prisma 2, and Postgres, we will learn how to read data from, and save data to our database.

  • 00:00 - Introduction
  • 05:40 - Setting up Prisma 2
  • 09:05 - Defining Prisma Schema
  • 14:30 - Next.js API Routes
  • 19:30 - Creating Sighting w/ Prisma
  • 24:20 - Fetching Sightings w/ Prisma
  • 27:01 - react-query useQuery
  • 31:30 - react-query useMutation
  • 40:00 - Optimistic UI w/ react-query

#react #next #prisma #postgres #database

Eva  Murphy

Eva Murphy

1625751960

Laravel API and React Next JS frontend development - 28

In this video, I wanted to touch upon the functionality of adding Chapters inside a Course. The idea was to not think much and start the development and pick up things as they come.

There are places where I get stuck and trying to find answers to it up doing what every developer does - Google and get help. I hope this will help you understand the flow and also how developers debug while doing development.

App url: https://video-reviews.vercel.app
Github code links below:
Next JS App: https://github.com/amitavroy/video-reviews
Laravel API: https://github.com/amitavdevzone/video-review-api

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #api #react next js #next #frontend #development