Как создать полнофункциональное приложение с помощью Next.js, Prisma

В этой статье мы узнаем, как создать полнофункциональное приложение с использованием Next.js, Prisma, Postgres и Fastify. Мы создадим демонстрационное приложение для управления посещаемостью, которое будет управлять посещаемостью сотрудников. Процесс работы приложения прост: пользователь с правами администратора входит в систему, создает лист посещаемости на день, затем каждый сотрудник входит и выходит из листа посещаемости.

Что такое Next.js?

Next.js — это гибкая среда React, которая дает вам строительные блоки для создания быстрых веб-приложений. Его часто называют фреймворком React с полным стеком, поскольку он позволяет использовать как интерфейсные, так и серверные приложения в одной кодовой базе, используя бессерверные функции.

Что такое Призма?

Prisma — это ORM с открытым исходным кодом, Node.js и Typescript, который значительно упрощает моделирование данных, миграцию и доступ к данным для баз данных SQL. На момент написания этой статьи Prisma поддерживает следующие системы управления базами данных: PostgreSQL, MySQL, MariaDB, SQLite, AWS Aurora, Microsoft SQL Server, Azure SQL и MongoDB. Вы также можете щелкнуть здесь, чтобы увидеть список всех поддерживаемых систем управления базами данных.

Что такое Постгрес?

Postgres также известен как PostgreSQL и представляет собой бесплатную систему управления реляционными базами данных с открытым исходным кодом. Это надмножество языка SQL, обладающее множеством функций, которые позволяют разработчикам безопасно хранить и масштабировать сложные рабочие нагрузки данных.

Предпосылки

Этот учебник представляет собой практическое демонстрационное руководство. Поэтому было бы лучше, если бы на вашем компьютере было установлено следующее:

Код для этого руководства доступен здесь , на Github, так что не стесняйтесь клонировать его и следовать дальше.

Настройка проекта

Начнем с настройки нашего приложения Next.js. Чтобы начать, выполните команду ниже.

npx create-next-app@latest

Дождитесь завершения установки, а затем выполните приведенную ниже команду, чтобы установить наши зависимости.

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

Дождитесь завершения установки.

Настройка Next.js и Fastify

По умолчанию Next.js не использует Fastify в качестве своего сервера. Чтобы использовать Fastify для обслуживания нашего приложения Next.js, отредактируйте поле scripts в package.jsonфайле с помощью приведенного ниже фрагмента кода.

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

Создание нашего сервера Fastify

Теперь давайте создадим server.jsфайл. Этот файл является точкой входа нашего приложения, а затем мы добавляем, require('fastify-nextjs')чтобы включить плагин, который предоставляет API Next.js в fastify для обработки рендеринга.

Откройте 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>')
})

В приведенном выше фрагменте кода мы используем fastify-nextjsплагин, предоставляющий API Next.js в Fastify, который обрабатывает рендеринг для нас. Затем мы анализируем входящие запросы с помощью noOpParserфункции, которая делает тело запроса доступным для наших обработчиков маршрутов API Next.js, и мы определяем два маршрута для нашего приложения с помощью [fastify.next](<http://fastify.next>команды. Затем мы создаем наш сервер Fastify и заставляем его слушать порт 3000.

Теперь запустите приложение с помощью yarn devкоманды: приложение будет работать на localhost:3000.

Настройка призмы

Сначала выполните следующую команду, чтобы получить базовую настройку Prisma:

npx prisma init

Приведенная выше команда создаст каталог prisma с schema.prismaфайлом. Это ваш основной файл конфигурации Prisma, который будет содержать схему вашей базы данных. Также .envфайл будет добавлен в корень проекта. Откройте .envфайл и замените фиктивный URL-адрес подключения на URL-адрес подключения вашей базы данных PostgreSQL.

Замените код в prisma/schema.prismaфайле следующим:

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
}

В приведенном выше фрагменте кода мы создали пользователя, таблицу посещаемости и модель посещаемости, определяя отношения между каждой моделью.

Затем создайте эти таблицы в базе данных. Выполните следующую команду:

npx prisma db push

После выполнения приведенной выше команды вы должны увидеть вывод, как показано на снимке экрана ниже, в вашем терминале:

Создание служебных функций

Закончив настройку Prisma, давайте создадим три служебные функции, которые будут время от времени использоваться в нашем приложении.

Откройте файл lib/parseBody.js и добавьте следующий фрагмент кода. Эта функция анализирует тело запроса в формате JSON:

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

Откройте файл lib/request.js и добавьте следующий фрагмент кода. Эта функция отправляет запрос POST.

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

Откройте файл /lib/request.js и добавьте следующий фрагмент кода. Эта функция возвращает объект свойств сеанса для железной сессии iron-session.

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 для этого приложения, поэтому откройте styles/Home.modules.cssфайл и добавьте фрагмент кода ниже:

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

Создайте компонент боковой панели

Создав стиль, давайте создадим компонент боковой панели, который поможет нам переходить на разные страницы на панели инструментов нашего приложения. Откройте файл components/SideBar.js и вставьте приведенный ниже фрагмент кода.

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:

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 входа

Теперь откройте файл page/api/login.js и добавьте следующий фрагмент кода. Мы будем использовать PrismaClientдля выполнения запросов к базе данных, и withIronSessionApiRouteэто функция Iron-Session для обработки пользовательских сеансов в приложениях RESTful.

Этот маршрут обрабатывает POST-запрос входа в систему по адресу localhost:3000/api/login и создает файлы cookie для аутентификации после аутентификации пользователя.

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 и добавьте приведенный ниже фрагмент кода. Этот маршрут обрабатывает запросы GET к localhost:3000/api/logout, которые выводят пользователей из системы, уничтожая файлы cookie сеанса.

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 и добавьте приведенный ниже фрагмент кода.

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для генерации данных страницы, и withIronSessionSsrэто функция сеанса железа для работы со страницами, отображаемыми на стороне сервера. В следующем фрагменте кода мы запрашиваем последнюю строку таблицы посещаемости со строкой из таблицы посещаемости, где userIdравно идентификатору пользователя, хранящемуся в пользовательском сеансе. Мы также проверяем, является ли пользователь администратором.

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

Настройка маршрута Create Attendance API

Откройте файл page/api/create-attendance.js и добавьте приведенный ниже фрагмент кода.

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

Настройка маршрута API посещаемости входа

Этот маршрут обрабатывает наш POST-запрос API к localhost:3000/api/sign-attendance. Маршрут принимает запрос POST, а attendanceSheetIdи actionиспользуются для входа и выхода из attendanceSheet.

Откройте файл /page/api/sign-attendance.js и добавьте приведенный ниже фрагмент кода.

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/attendance.js и добавьте приведенный ниже фрагмент кода.

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

В приведенном ниже фрагменте кода мы запрашиваем все строки из attendanceSheetтаблицы, а также получаем посещаемость, где userIdравно идентификатору пользователя, хранящемуся в сеансе пользователя.

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/attendance.js и добавьте приведенный ниже фрагмент кода.

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

В приведенном ниже фрагменте кода мы запрашиваем все строки из attendanceSheetтаблиц, а также получаем данные о посещаемости, выбирая имя, адрес электронной почты и роль.

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 Studio. Чтобы запустить Prisma studio, выполните следующую команду:

npx prisma studio

Индексная страница Prisma выглядит так:

Индексная страница Prisma


Чтобы создать пользователя базы данных с ролью ADMIN и нескольких пользователей с ролью EMPLOYEE, перейдите на эту страницу:

Добавление пользователя

Нажмите «Добавить запись», затем заполните необходимые поля: пароль, имя, адрес электронной почты и роль. Когда вы закончите, нажмите зеленую кнопку «Сохранить 1 изменение». Обратите внимание, что для простоты мы не хэшируем пароль.

Запустите сервер с помощью yarn dev. Это запускает сервер и запускает приложение на [localhost:3000](<http://localhost:3000>), страница входа показана ниже.

Страница входа

Войдите в систему с помощью пользователя с ролью ADMIN, поскольку только пользователи с правами администратора могут создавать листы посещаемости. После успешного входа приложение перенаправит вас на панель инструментов.

Нажмите кнопку «Создать лист посещаемости», чтобы создать лист посещаемости, затем подождите, пока запрос завершится, и появится лист посещаемости. Панель управления пользователя показана ниже.

Создание листа посещаемости

Лист посещаемости показан ниже, нажмите кнопку «Войти», чтобы войти в систему. После успешного входа отобразится время входа и кнопка «Выйти». Нажмите кнопку «Выход», чтобы выйти, и повторите этот процесс много раз с разными пользователями.

Успешный вход

Затем нажмите ссылку «Посещаемость» на боковой панели, чтобы просмотреть посещаемость пользователей. Результаты должны совпадать с показанными ниже:

Страница посещаемости

Затем нажмите ссылку «Лист посещаемости» на боковой панели, чтобы просмотреть посещаемость всех пользователей. Результаты показаны ниже:

Страница листа посещаемости

Вывод

В этой статье вы узнали, как использовать собственный сервер Fastify с Next.js. Вы также узнали о Prisma и студии Prisma. Я рассказал вам, как подключить Prisma к базе данных Postgres и как создавать, читать и обновлять базу данных с помощью клиента Prisma и студии Prisma.

Ссылка: https://arctype.com/blog/fullstack-nextjs-postgres-fastify/

#fullstack #nextjs #postgre #fastify

What is GEEK

Buddha Community

Как создать полнофункциональное приложение с помощью Next.js, Prisma

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

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

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

Как создать полнофункциональное приложение с помощью Next.js, Prisma

В этой статье мы узнаем, как создать полнофункциональное приложение с использованием Next.js, Prisma, Postgres и Fastify. Мы создадим демонстрационное приложение для управления посещаемостью, которое будет управлять посещаемостью сотрудников. Процесс работы приложения прост: пользователь с правами администратора входит в систему, создает лист посещаемости на день, затем каждый сотрудник входит и выходит из листа посещаемости.

Что такое Next.js?

Next.js — это гибкая среда React, которая дает вам строительные блоки для создания быстрых веб-приложений. Его часто называют фреймворком React с полным стеком, поскольку он позволяет использовать как интерфейсные, так и серверные приложения в одной кодовой базе, используя бессерверные функции.

Что такое Призма?

Prisma — это ORM с открытым исходным кодом, Node.js и Typescript, который значительно упрощает моделирование данных, миграцию и доступ к данным для баз данных SQL. На момент написания этой статьи Prisma поддерживает следующие системы управления базами данных: PostgreSQL, MySQL, MariaDB, SQLite, AWS Aurora, Microsoft SQL Server, Azure SQL и MongoDB. Вы также можете щелкнуть здесь, чтобы увидеть список всех поддерживаемых систем управления базами данных.

Что такое Постгрес?

Postgres также известен как PostgreSQL и представляет собой бесплатную систему управления реляционными базами данных с открытым исходным кодом. Это надмножество языка SQL, обладающее множеством функций, которые позволяют разработчикам безопасно хранить и масштабировать сложные рабочие нагрузки данных.

Предпосылки

Этот учебник представляет собой практическое демонстрационное руководство. Поэтому было бы лучше, если бы на вашем компьютере было установлено следующее:

Код для этого руководства доступен здесь , на Github, так что не стесняйтесь клонировать его и следовать дальше.

Настройка проекта

Начнем с настройки нашего приложения Next.js. Чтобы начать, выполните команду ниже.

npx create-next-app@latest

Дождитесь завершения установки, а затем выполните приведенную ниже команду, чтобы установить наши зависимости.

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

Дождитесь завершения установки.

Настройка Next.js и Fastify

По умолчанию Next.js не использует Fastify в качестве своего сервера. Чтобы использовать Fastify для обслуживания нашего приложения Next.js, отредактируйте поле scripts в package.jsonфайле с помощью приведенного ниже фрагмента кода.

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

Создание нашего сервера Fastify

Теперь давайте создадим server.jsфайл. Этот файл является точкой входа нашего приложения, а затем мы добавляем, require('fastify-nextjs')чтобы включить плагин, который предоставляет API Next.js в fastify для обработки рендеринга.

Откройте 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>')
})

В приведенном выше фрагменте кода мы используем fastify-nextjsплагин, предоставляющий API Next.js в Fastify, который обрабатывает рендеринг для нас. Затем мы анализируем входящие запросы с помощью noOpParserфункции, которая делает тело запроса доступным для наших обработчиков маршрутов API Next.js, и мы определяем два маршрута для нашего приложения с помощью [fastify.next](<http://fastify.next>команды. Затем мы создаем наш сервер Fastify и заставляем его слушать порт 3000.

Теперь запустите приложение с помощью yarn devкоманды: приложение будет работать на localhost:3000.

Настройка призмы

Сначала выполните следующую команду, чтобы получить базовую настройку Prisma:

npx prisma init

Приведенная выше команда создаст каталог prisma с schema.prismaфайлом. Это ваш основной файл конфигурации Prisma, который будет содержать схему вашей базы данных. Также .envфайл будет добавлен в корень проекта. Откройте .envфайл и замените фиктивный URL-адрес подключения на URL-адрес подключения вашей базы данных PostgreSQL.

Замените код в prisma/schema.prismaфайле следующим:

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
}

В приведенном выше фрагменте кода мы создали пользователя, таблицу посещаемости и модель посещаемости, определяя отношения между каждой моделью.

Затем создайте эти таблицы в базе данных. Выполните следующую команду:

npx prisma db push

После выполнения приведенной выше команды вы должны увидеть вывод, как показано на снимке экрана ниже, в вашем терминале:

Создание служебных функций

Закончив настройку Prisma, давайте создадим три служебные функции, которые будут время от времени использоваться в нашем приложении.

Откройте файл lib/parseBody.js и добавьте следующий фрагмент кода. Эта функция анализирует тело запроса в формате JSON:

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

Откройте файл lib/request.js и добавьте следующий фрагмент кода. Эта функция отправляет запрос POST.

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

Откройте файл /lib/request.js и добавьте следующий фрагмент кода. Эта функция возвращает объект свойств сеанса для железной сессии iron-session.

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 для этого приложения, поэтому откройте styles/Home.modules.cssфайл и добавьте фрагмент кода ниже:

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

Создайте компонент боковой панели

Создав стиль, давайте создадим компонент боковой панели, который поможет нам переходить на разные страницы на панели инструментов нашего приложения. Откройте файл components/SideBar.js и вставьте приведенный ниже фрагмент кода.

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:

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 входа

Теперь откройте файл page/api/login.js и добавьте следующий фрагмент кода. Мы будем использовать PrismaClientдля выполнения запросов к базе данных, и withIronSessionApiRouteэто функция Iron-Session для обработки пользовательских сеансов в приложениях RESTful.

Этот маршрут обрабатывает POST-запрос входа в систему по адресу localhost:3000/api/login и создает файлы cookie для аутентификации после аутентификации пользователя.

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 и добавьте приведенный ниже фрагмент кода. Этот маршрут обрабатывает запросы GET к localhost:3000/api/logout, которые выводят пользователей из системы, уничтожая файлы cookie сеанса.

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 и добавьте приведенный ниже фрагмент кода.

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для генерации данных страницы, и withIronSessionSsrэто функция сеанса железа для работы со страницами, отображаемыми на стороне сервера. В следующем фрагменте кода мы запрашиваем последнюю строку таблицы посещаемости со строкой из таблицы посещаемости, где userIdравно идентификатору пользователя, хранящемуся в пользовательском сеансе. Мы также проверяем, является ли пользователь администратором.

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

Настройка маршрута Create Attendance API

Откройте файл page/api/create-attendance.js и добавьте приведенный ниже фрагмент кода.

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

Настройка маршрута API посещаемости входа

Этот маршрут обрабатывает наш POST-запрос API к localhost:3000/api/sign-attendance. Маршрут принимает запрос POST, а attendanceSheetIdи actionиспользуются для входа и выхода из attendanceSheet.

Откройте файл /page/api/sign-attendance.js и добавьте приведенный ниже фрагмент кода.

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/attendance.js и добавьте приведенный ниже фрагмент кода.

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

В приведенном ниже фрагменте кода мы запрашиваем все строки из attendanceSheetтаблицы, а также получаем посещаемость, где userIdравно идентификатору пользователя, хранящемуся в сеансе пользователя.

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/attendance.js и добавьте приведенный ниже фрагмент кода.

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

В приведенном ниже фрагменте кода мы запрашиваем все строки из attendanceSheetтаблиц, а также получаем данные о посещаемости, выбирая имя, адрес электронной почты и роль.

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 Studio. Чтобы запустить Prisma studio, выполните следующую команду:

npx prisma studio

Индексная страница Prisma выглядит так:

Индексная страница Prisma


Чтобы создать пользователя базы данных с ролью ADMIN и нескольких пользователей с ролью EMPLOYEE, перейдите на эту страницу:

Добавление пользователя

Нажмите «Добавить запись», затем заполните необходимые поля: пароль, имя, адрес электронной почты и роль. Когда вы закончите, нажмите зеленую кнопку «Сохранить 1 изменение». Обратите внимание, что для простоты мы не хэшируем пароль.

Запустите сервер с помощью yarn dev. Это запускает сервер и запускает приложение на [localhost:3000](<http://localhost:3000>), страница входа показана ниже.

Страница входа

Войдите в систему с помощью пользователя с ролью ADMIN, поскольку только пользователи с правами администратора могут создавать листы посещаемости. После успешного входа приложение перенаправит вас на панель инструментов.

Нажмите кнопку «Создать лист посещаемости», чтобы создать лист посещаемости, затем подождите, пока запрос завершится, и появится лист посещаемости. Панель управления пользователя показана ниже.

Создание листа посещаемости

Лист посещаемости показан ниже, нажмите кнопку «Войти», чтобы войти в систему. После успешного входа отобразится время входа и кнопка «Выйти». Нажмите кнопку «Выход», чтобы выйти, и повторите этот процесс много раз с разными пользователями.

Успешный вход

Затем нажмите ссылку «Посещаемость» на боковой панели, чтобы просмотреть посещаемость пользователей. Результаты должны совпадать с показанными ниже:

Страница посещаемости

Затем нажмите ссылку «Лист посещаемости» на боковой панели, чтобы просмотреть посещаемость всех пользователей. Результаты показаны ниже:

Страница листа посещаемости

Вывод

В этой статье вы узнали, как использовать собственный сервер Fastify с Next.js. Вы также узнали о Prisma и студии Prisma. Я рассказал вам, как подключить Prisma к базе данных Postgres и как создавать, читать и обновлять базу данных с помощью клиента Prisma и студии Prisma.

Ссылка: https://arctype.com/blog/fullstack-nextjs-postgres-fastify/

#fullstack #nextjs #postgre #fastify

Lilyan  Streich

Lilyan Streich

1599119110

Next js Tutorial For Beginners

Next js Tutorial For Beginners is the today’s topic. It is no secret that creating single-page applications can be immensely challenging these days. But with the help of some libraries, frameworks, and tools, it is effortless nowadays. React.js is the common frontend libraries among the Front-end developers. Its virtual dom theory makes React faster and gives us the better application performance. Now, one problem is that Single Page Applications are not at all SEO  friendly because it is rendered on the Client side  and not Server side . So when the Search Engine crawlers try to send a request, they cannot get our meta content or description and not even the main content. Search Engines do not care about how your app is architected or whatever ideology was used to adjust and fetch the right material. Their bots are not as smart as using your apps as a real user would. All they care about is that once they send their spiders to crawl and index your site, whatever the server provides on the first request is what gets indexed. In our case, all they get is our div tag with an id and bundled JS file, and we can not index our website correctly. So some how, we need a SSR to tackle this problem and in React js, Next.js is the perfect solution.

#js #react.js #next.js