Thierry  Perret

Thierry Perret

1657269207

Comment Créer Une Application Complète Avec Next.js, Prisma, Postgres

Dans cet article, nous allons apprendre à créer une application Full-stack à l'aide de Next.js, Prisma, Postgres et Fastify. Nous allons créer une application de démonstration de gestion des présences qui gère les présences des employés. Le déroulement de l'application est simple : un utilisateur administratif se connecte, crée une feuille de présence pour la journée, puis chaque employé se connecte et se déconnecte de la feuille de présence.

Qu'est-ce que Next.js ?

Next.js est un framework React flexible qui vous donne des éléments de base pour créer des applications Web rapides. Il est souvent appelé le framework React fullstack car il permet d'avoir des applications frontend et backend sur la même base de code avec des fonctions sans serveur.

Qu'est-ce que Prisma ?

Prisma est un ORM open-source, Node.js et Typescript qui simplifie considérablement la modélisation des données, les migrations et l'accès aux données pour les bases de données SQL. Au moment de la rédaction de cet article, Prisma prend en charge les systèmes de gestion de base de données suivants : PostgreSQL, MySQL, MariaDB, SQLite, AWS Aurora, Microsoft SQL Server, Azure SQL et MongoDB. Vous pouvez également cliquer ici pour voir la liste de tous les systèmes de gestion de base de données pris en charge.

Qu'est-ce que Postgres ?

Postgres est également connu sous le nom de PostgreSQL et c'est un système de gestion de base de données relationnelle libre et open source. Il s'agit d'un sur-ensemble du langage SQL, et il possède de nombreuses fonctionnalités qui permettent aux développeurs de stocker et de mettre à l'échelle en toute sécurité des charges de travail de données complexes.

Conditions préalables

Ce didacticiel est un didacticiel de démonstration pratique. Par conséquent, il serait préférable que les éléments suivants soient installés sur votre ordinateur pour suivre :

Le code de ce tutoriel est disponible ici sur Github, alors n'hésitez pas à le cloner et à suivre.

Configuration du projet

Commençons par configurer notre application Next.js. Pour commencer, exécutez la commande ci-dessous.

npx create-next-app@latest

Attendez que l'installation soit terminée, puis exécutez la commande ci-dessous pour installer nos dépendances.

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

Attendez que l'installation soit terminée.

Configurer Next.js et Fastify

Par défaut, Next.js n'utilise pas Fastify comme serveur. Pour utiliser Fastify pour servir notre application Next.js, modifiez le champ des scripts dans le package.jsonfichier avec l'extrait de code ci-dessous.

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

Création de notre serveur Fastify

Créons maintenant un server.jsfichier. Ce fichier est le point d'entrée de notre application, puis nous ajoutons le require('fastify-nextjs')pour inclure le plugin qui expose l'API Next.js dans fastify pour gérer le rendu.

Ouvrez le server.jsfichier et ajoutez les extraits de code ci-dessous :

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

Dans l'extrait de code ci-dessus, nous utilisons le fastify-nextjsplugin qui a exposé l'API Next.js dans Fastify qui gère le rendu pour nous. Ensuite, nous analysons les requêtes entrantes avec la noOpParserfonction qui met le corps de la requête à la disposition de nos gestionnaires de routes API Next.js et nous définissons deux routes pour notre application avec la [fastify.next](<http://fastify.next>commande. Ensuite, nous créons notre serveur Fastify et le faisons écouter le port 3000.

Maintenant, lancez l'application avec la yarn devcommande : l'application s'exécutera sur localhost:3000.

Configuration de Prisma

Tout d'abord, exécutez la commande suivante pour obtenir une configuration de base de Prisma :

npx prisma init

La commande ci-dessus créera un répertoire prisma avec un schema.prismafichier. Il s'agit de votre fichier de configuration Prisma principal qui contiendra le schéma de votre base de données. De plus, un .envfichier sera ajouté à la racine du projet. Ouvrez le .envfichier et remplacez l'URL de connexion factice par l'URL de connexion de votre base de données PostgreSQL.

Remplacez le code dans le prisma/schema.prismafichier par ce qui suit :

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
}

Dans l'extrait de code ci-dessus, nous avons créé un utilisateur, une feuille de présence et un modèle de présence, définissant les relations entre chaque modèle.

Ensuite, créez ces tables dans la base de données. Exécutez la commande suivante :

npx prisma db push

Après avoir exécuté la commande ci-dessus, vous devriez voir la sortie comme indiqué dans la capture d'écran ci-dessous dans votre terminal :

Création de fonctions utilitaires

Une fois la configuration de Prisma terminée, créons trois fonctions utilitaires qui seront utilisées de temps à autre dans notre application.

Ouvrez le fichier lib/parseBody.js et ajoutez l'extrait de code suivant. Cette fonction analyse le corps de la requête en JSON :

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

Ouvrez le fichier lib/request.js et ajoutez l'extrait de code suivant. Cette fonction envoie une requête POST.

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

Ouvrez le fichier /lib/request.js et ajoutez l'extrait de code suivant. Cette fonction renvoie un objet de propriétés de session pour iron session 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",
},
})
}

Ensuite, ajoutez SESSION_PASSWORDau fichier .env : il doit s'agir d'une chaîne d'au moins 32 caractères.

Styliser l'application

Une fois nos fonctions utilitaires terminées, ajoutons quelques styles à l'application. Nous utilisons des modules CSS pour cette application, alors ouvrez le styles/Home.modules.cssfichier et ajoutez l'extrait de code ci-dessous :

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

Créer le composant de la barre latérale

Une fois notre style terminé, créons le composant de la barre latérale pour nous aider à naviguer vers différentes pages du tableau de bord de notre application. Ouvrez le fichier components/SideBar.js et collez l'extrait de code ci-dessous.

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 de connexion

Ouvrez maintenant le fichier page/index.js, supprimez tout le code qu'il contient et ajoutez l'extrait de code suivant. Le code ci-dessous envoie une demande de publication avec l'e-mail et le mot de passe fournis via le formulaire à la route localhost:3000/api/login. Une fois les informations d'identification validées, il appelle la router.push('/dashboard')méthode qui redirige l'utilisateur vers 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>
  )
}

Configuration de la route de l'API de connexion

Ouvrez maintenant le fichier page/api/login.js et ajoutez l'extrait de code suivant. Nous utiliserons PrismaClientpour effectuer nos requêtes de base de données, et withIronSessionApiRouteest la fonction iron-session pour gérer les sessions utilisateur dans les applications RESTful.

Cette route gère la requête POST de connexion vers localhost:3000/api/login et génère des cookies d'authentification une fois l'utilisateur authentifié.

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

Configuration de la route de l'API de déconnexion

Ouvrez le fichier /page/api/logout et ajoutez l'extrait de code ci-dessous. Cette route gère les requêtes GET vers localhost:3000/api/logout qui déconnecte les utilisateurs en détruisant les cookies de session.

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

Création de la page du tableau de bord

Cette page fournit une interface permettant aux utilisateurs de se connecter et de se déconnecter de la feuille de présence. Les administrateurs peuvent également créer une feuille de présence. Ouvrez le fichier page/dashboard/index.js et ajoutez l'extrait de code ci-dessous.

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

Nous utilisons le getServerSidePropspour générer les données de la page, et withIronSessionSsrest la fonction iron-session pour travailler avec les pages rendues côté serveur. Dans l'extrait de code suivant, nous recherchons la dernière ligne de la table presenceSheet avec une ligne de la table de présence, où userIdest égal à l'ID utilisateur stocké sur la session utilisateur. Nous vérifions également si l'utilisateur est un ADMIN.

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

Configuration de la route de l'API de création de présence

Ouvrez le fichier page/api/create-attendance.js et ajoutez l'extrait de code ci-dessous.

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

Configuration de la route de l'API Sign Attendance

Cette route gère notre requête API POST à ​​localhost:3000/api/sign-attendance. La route accepte la requête POST, tandis que attendanceSheetIdet actionsont utilisés pour se connecter et se déconnecter de attendanceSheet.

Ouvrez le fichier /page/api/sign-attendance.js et ajoutez l'extrait de code ci-dessous.

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

Création de la page de présence

Cette page rendue côté serveur affiche toutes les feuilles de présence d'un utilisateur connecté. Ouvrez le fichier /page/dashboard/attendance.js et ajoutez l'extrait de code ci-dessous.

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

Dans l'extrait de code ci-dessous, nous interrogeons toutes les lignes de la attendanceSheettable et récupérons également la présence où userIdest égal à l'ID utilisateur stocké dans la session utilisateur.

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

Création de la page Feuille de présence

Cette page rendue côté serveur affiche toutes les feuilles de présence et les employés qui se sont connectés à cette feuille de présence. Ouvrez le fichier /page/dashboard/attendance.js et ajoutez l'extrait de code ci-dessous.

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

Dans l'extrait de code ci-dessous, nous interrogeons toutes les lignes des attendanceSheettables et récupérons également la participation en sélectionnant le nom, l'e-mail et le rôle.

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

Tester l'application

Tout d'abord, nous devons ajouter des utilisateurs à notre base de données. Nous allons le faire avec Prisma Studio. Pour démarrer Prisma studio, exécutez la commande ci-dessous :

npx prisma studio

La page d'index de Prisma ressemble à ceci :

La page d'index de Prisma


Pour créer un utilisateur de base de données avec un rôle ADMIN et plusieurs utilisateurs avec un rôle EMPLOYEE, rendez-vous sur cette page :

Ajout d'un utilisateur

Cliquez sur Ajouter un enregistrement, puis renseignez les champs obligatoires : mot de passe, nom, email et rôle. Une fois que vous avez terminé, cliquez sur le bouton vert Enregistrer 1 modification. Notez que pour plus de simplicité, nous n'avons pas haché le mot de passe.

Démarrez le serveur avec yarn dev. Cela démarre le serveur et exécute l'application sur [localhost:3000](<http://localhost:3000>) la page de connexion est affichée ci-dessous.

La page de connexion

Connectez-vous avec un utilisateur ayant un rôle ADMIN car seuls les utilisateurs administratifs peuvent créer des feuilles de présence. Une fois la connexion réussie, l'application vous redirigera vers votre tableau de bord.

Cliquez sur le bouton Créer une feuille de présence pour créer une feuille de présence, puis attendez que la demande se termine et la feuille de présence apparaîtra. Le tableau de bord de l'utilisateur est illustré ci-dessous.

Création d'une feuille de présence

La feuille de présence est affichée ci-dessous, cliquez sur le bouton Connexion pour vous connecter. Une fois la connexion réussie, l'heure de connexion sera affichée et le bouton Déconnexion sera visible. Cliquez sur le bouton Se déconnecter pour vous déconnecter et répétez ce processus plusieurs fois avec différents utilisateurs.

Connexion réussie

Cliquez ensuite sur le lien Présence dans la barre latérale pour afficher la présence des utilisateurs. Les résultats doivent correspondre à ceux indiqués ci-dessous :

la page de présence

Cliquez ensuite sur le lien Feuille de présence dans la barre latérale pour afficher la présence de tous les utilisateurs. Les résultats sont présentés ci-dessous :

La page Feuille de présence

Conclusion

Dans cet article, vous avez appris à utiliser un serveur Fastify personnalisé avec Next.js. Vous avez également découvert Prisma et le studio Prisma. Je vous ai expliqué comment connecter Prisma à une base de données Postgres et comment créer, lire et mettre à jour la base de données à l'aide du client Prisma et du studio Prisma.

Lien : https://arctype.com/blog/fullstack-nextjs-postgres-fastify/

#fullstack #nextjs #postgre #fastify

What is GEEK

Buddha Community

Comment Créer Une Application Complète Avec 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

Thierry  Perret

Thierry Perret

1657269207

Comment Créer Une Application Complète Avec Next.js, Prisma, Postgres

Dans cet article, nous allons apprendre à créer une application Full-stack à l'aide de Next.js, Prisma, Postgres et Fastify. Nous allons créer une application de démonstration de gestion des présences qui gère les présences des employés. Le déroulement de l'application est simple : un utilisateur administratif se connecte, crée une feuille de présence pour la journée, puis chaque employé se connecte et se déconnecte de la feuille de présence.

Qu'est-ce que Next.js ?

Next.js est un framework React flexible qui vous donne des éléments de base pour créer des applications Web rapides. Il est souvent appelé le framework React fullstack car il permet d'avoir des applications frontend et backend sur la même base de code avec des fonctions sans serveur.

Qu'est-ce que Prisma ?

Prisma est un ORM open-source, Node.js et Typescript qui simplifie considérablement la modélisation des données, les migrations et l'accès aux données pour les bases de données SQL. Au moment de la rédaction de cet article, Prisma prend en charge les systèmes de gestion de base de données suivants : PostgreSQL, MySQL, MariaDB, SQLite, AWS Aurora, Microsoft SQL Server, Azure SQL et MongoDB. Vous pouvez également cliquer ici pour voir la liste de tous les systèmes de gestion de base de données pris en charge.

Qu'est-ce que Postgres ?

Postgres est également connu sous le nom de PostgreSQL et c'est un système de gestion de base de données relationnelle libre et open source. Il s'agit d'un sur-ensemble du langage SQL, et il possède de nombreuses fonctionnalités qui permettent aux développeurs de stocker et de mettre à l'échelle en toute sécurité des charges de travail de données complexes.

Conditions préalables

Ce didacticiel est un didacticiel de démonstration pratique. Par conséquent, il serait préférable que les éléments suivants soient installés sur votre ordinateur pour suivre :

Le code de ce tutoriel est disponible ici sur Github, alors n'hésitez pas à le cloner et à suivre.

Configuration du projet

Commençons par configurer notre application Next.js. Pour commencer, exécutez la commande ci-dessous.

npx create-next-app@latest

Attendez que l'installation soit terminée, puis exécutez la commande ci-dessous pour installer nos dépendances.

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

Attendez que l'installation soit terminée.

Configurer Next.js et Fastify

Par défaut, Next.js n'utilise pas Fastify comme serveur. Pour utiliser Fastify pour servir notre application Next.js, modifiez le champ des scripts dans le package.jsonfichier avec l'extrait de code ci-dessous.

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

Création de notre serveur Fastify

Créons maintenant un server.jsfichier. Ce fichier est le point d'entrée de notre application, puis nous ajoutons le require('fastify-nextjs')pour inclure le plugin qui expose l'API Next.js dans fastify pour gérer le rendu.

Ouvrez le server.jsfichier et ajoutez les extraits de code ci-dessous :

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

Dans l'extrait de code ci-dessus, nous utilisons le fastify-nextjsplugin qui a exposé l'API Next.js dans Fastify qui gère le rendu pour nous. Ensuite, nous analysons les requêtes entrantes avec la noOpParserfonction qui met le corps de la requête à la disposition de nos gestionnaires de routes API Next.js et nous définissons deux routes pour notre application avec la [fastify.next](<http://fastify.next>commande. Ensuite, nous créons notre serveur Fastify et le faisons écouter le port 3000.

Maintenant, lancez l'application avec la yarn devcommande : l'application s'exécutera sur localhost:3000.

Configuration de Prisma

Tout d'abord, exécutez la commande suivante pour obtenir une configuration de base de Prisma :

npx prisma init

La commande ci-dessus créera un répertoire prisma avec un schema.prismafichier. Il s'agit de votre fichier de configuration Prisma principal qui contiendra le schéma de votre base de données. De plus, un .envfichier sera ajouté à la racine du projet. Ouvrez le .envfichier et remplacez l'URL de connexion factice par l'URL de connexion de votre base de données PostgreSQL.

Remplacez le code dans le prisma/schema.prismafichier par ce qui suit :

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
}

Dans l'extrait de code ci-dessus, nous avons créé un utilisateur, une feuille de présence et un modèle de présence, définissant les relations entre chaque modèle.

Ensuite, créez ces tables dans la base de données. Exécutez la commande suivante :

npx prisma db push

Après avoir exécuté la commande ci-dessus, vous devriez voir la sortie comme indiqué dans la capture d'écran ci-dessous dans votre terminal :

Création de fonctions utilitaires

Une fois la configuration de Prisma terminée, créons trois fonctions utilitaires qui seront utilisées de temps à autre dans notre application.

Ouvrez le fichier lib/parseBody.js et ajoutez l'extrait de code suivant. Cette fonction analyse le corps de la requête en JSON :

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

Ouvrez le fichier lib/request.js et ajoutez l'extrait de code suivant. Cette fonction envoie une requête POST.

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

Ouvrez le fichier /lib/request.js et ajoutez l'extrait de code suivant. Cette fonction renvoie un objet de propriétés de session pour iron session 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",
},
})
}

Ensuite, ajoutez SESSION_PASSWORDau fichier .env : il doit s'agir d'une chaîne d'au moins 32 caractères.

Styliser l'application

Une fois nos fonctions utilitaires terminées, ajoutons quelques styles à l'application. Nous utilisons des modules CSS pour cette application, alors ouvrez le styles/Home.modules.cssfichier et ajoutez l'extrait de code ci-dessous :

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

Créer le composant de la barre latérale

Une fois notre style terminé, créons le composant de la barre latérale pour nous aider à naviguer vers différentes pages du tableau de bord de notre application. Ouvrez le fichier components/SideBar.js et collez l'extrait de code ci-dessous.

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 de connexion

Ouvrez maintenant le fichier page/index.js, supprimez tout le code qu'il contient et ajoutez l'extrait de code suivant. Le code ci-dessous envoie une demande de publication avec l'e-mail et le mot de passe fournis via le formulaire à la route localhost:3000/api/login. Une fois les informations d'identification validées, il appelle la router.push('/dashboard')méthode qui redirige l'utilisateur vers 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>
  )
}

Configuration de la route de l'API de connexion

Ouvrez maintenant le fichier page/api/login.js et ajoutez l'extrait de code suivant. Nous utiliserons PrismaClientpour effectuer nos requêtes de base de données, et withIronSessionApiRouteest la fonction iron-session pour gérer les sessions utilisateur dans les applications RESTful.

Cette route gère la requête POST de connexion vers localhost:3000/api/login et génère des cookies d'authentification une fois l'utilisateur authentifié.

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

Configuration de la route de l'API de déconnexion

Ouvrez le fichier /page/api/logout et ajoutez l'extrait de code ci-dessous. Cette route gère les requêtes GET vers localhost:3000/api/logout qui déconnecte les utilisateurs en détruisant les cookies de session.

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

Création de la page du tableau de bord

Cette page fournit une interface permettant aux utilisateurs de se connecter et de se déconnecter de la feuille de présence. Les administrateurs peuvent également créer une feuille de présence. Ouvrez le fichier page/dashboard/index.js et ajoutez l'extrait de code ci-dessous.

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

Nous utilisons le getServerSidePropspour générer les données de la page, et withIronSessionSsrest la fonction iron-session pour travailler avec les pages rendues côté serveur. Dans l'extrait de code suivant, nous recherchons la dernière ligne de la table presenceSheet avec une ligne de la table de présence, où userIdest égal à l'ID utilisateur stocké sur la session utilisateur. Nous vérifions également si l'utilisateur est un ADMIN.

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

Configuration de la route de l'API de création de présence

Ouvrez le fichier page/api/create-attendance.js et ajoutez l'extrait de code ci-dessous.

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

Configuration de la route de l'API Sign Attendance

Cette route gère notre requête API POST à ​​localhost:3000/api/sign-attendance. La route accepte la requête POST, tandis que attendanceSheetIdet actionsont utilisés pour se connecter et se déconnecter de attendanceSheet.

Ouvrez le fichier /page/api/sign-attendance.js et ajoutez l'extrait de code ci-dessous.

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

Création de la page de présence

Cette page rendue côté serveur affiche toutes les feuilles de présence d'un utilisateur connecté. Ouvrez le fichier /page/dashboard/attendance.js et ajoutez l'extrait de code ci-dessous.

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

Dans l'extrait de code ci-dessous, nous interrogeons toutes les lignes de la attendanceSheettable et récupérons également la présence où userIdest égal à l'ID utilisateur stocké dans la session utilisateur.

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

Création de la page Feuille de présence

Cette page rendue côté serveur affiche toutes les feuilles de présence et les employés qui se sont connectés à cette feuille de présence. Ouvrez le fichier /page/dashboard/attendance.js et ajoutez l'extrait de code ci-dessous.

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

Dans l'extrait de code ci-dessous, nous interrogeons toutes les lignes des attendanceSheettables et récupérons également la participation en sélectionnant le nom, l'e-mail et le rôle.

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

Tester l'application

Tout d'abord, nous devons ajouter des utilisateurs à notre base de données. Nous allons le faire avec Prisma Studio. Pour démarrer Prisma studio, exécutez la commande ci-dessous :

npx prisma studio

La page d'index de Prisma ressemble à ceci :

La page d'index de Prisma


Pour créer un utilisateur de base de données avec un rôle ADMIN et plusieurs utilisateurs avec un rôle EMPLOYEE, rendez-vous sur cette page :

Ajout d'un utilisateur

Cliquez sur Ajouter un enregistrement, puis renseignez les champs obligatoires : mot de passe, nom, email et rôle. Une fois que vous avez terminé, cliquez sur le bouton vert Enregistrer 1 modification. Notez que pour plus de simplicité, nous n'avons pas haché le mot de passe.

Démarrez le serveur avec yarn dev. Cela démarre le serveur et exécute l'application sur [localhost:3000](<http://localhost:3000>) la page de connexion est affichée ci-dessous.

La page de connexion

Connectez-vous avec un utilisateur ayant un rôle ADMIN car seuls les utilisateurs administratifs peuvent créer des feuilles de présence. Une fois la connexion réussie, l'application vous redirigera vers votre tableau de bord.

Cliquez sur le bouton Créer une feuille de présence pour créer une feuille de présence, puis attendez que la demande se termine et la feuille de présence apparaîtra. Le tableau de bord de l'utilisateur est illustré ci-dessous.

Création d'une feuille de présence

La feuille de présence est affichée ci-dessous, cliquez sur le bouton Connexion pour vous connecter. Une fois la connexion réussie, l'heure de connexion sera affichée et le bouton Déconnexion sera visible. Cliquez sur le bouton Se déconnecter pour vous déconnecter et répétez ce processus plusieurs fois avec différents utilisateurs.

Connexion réussie

Cliquez ensuite sur le lien Présence dans la barre latérale pour afficher la présence des utilisateurs. Les résultats doivent correspondre à ceux indiqués ci-dessous :

la page de présence

Cliquez ensuite sur le lien Feuille de présence dans la barre latérale pour afficher la présence de tous les utilisateurs. Les résultats sont présentés ci-dessous :

La page Feuille de présence

Conclusion

Dans cet article, vous avez appris à utiliser un serveur Fastify personnalisé avec Next.js. Vous avez également découvert Prisma et le studio Prisma. Je vous ai expliqué comment connecter Prisma à une base de données Postgres et comment créer, lire et mettre à jour la base de données à l'aide du client Prisma et du studio Prisma.

Lien : 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

Benefits of Angular JS based Applications

AngularJS was introduced in the year 2009, by Google. AngularJS is a software framework used worldwide by developers. The entire base of this framework is open source. AngularJS has gained popularity among developers because of how it has become for them to create web applications. AngularJS helps in building apps that require less work and reduces the use of unnecessary codes. AngularJS application development is a javascript framework. AngularJS has a clear goal to make the entire process simpler, it also helps app development process and operations as much as it could. AngularJS is used for building applications that support MVC (model view controller) and SPAs (single page web apps) coding and programming structures. AngularJS has been used by some of the top companies in the world to simplify their app development process, like, Google, Paypal, Udemy, mobile site in iPad for HBO, etc. To read more click on the link.

#hire angular js developer #hire dedicated angular js developer #angular js application development #hire dedicated angular js team #hire best angular js application developer