Thursday, March 28, 2024
HomeWeb developmentConstruct a Code Snippet Net App with Subsequent.js and FaunaDB

Construct a Code Snippet Net App with Subsequent.js and FaunaDB


Whereas programming, builders encounter issues requiring code reuse, resulting in repetitive programming that may be time-wasting and reduces productiveness. This offers rise to the necessity for reusable supply code known as “code snippets”. These snippets forestall repetitive code throughout programming, could be saved for future use, and are sharable.

On this tutorial, we’ll construct a web site to assist customers save day by day code snippets utilizing the Subsequent.js internet improvement framework, and powered by the Fauna database to deal with the storage, manipulation, and show of code snippets. By engaged on this enjoyable mission, we’ll additionally discover ways to create a fundamental CRUD app with Subsequent.js and FaunaDB that may be used for constructing different comparable initiatives.

A working model of this mission could be discovered at GitHub. To comply with alongside, you’ll want Node put in in your machine, in addition to a FaunaDB account and a Google account (for authentication).

Putting in Dependencies

On this part, we’ll have a look at tips on how to set up Subsequent.js utilizing the npx create-next-app command. It will initialize the Subsequent CLI and construct a brand new Subsequent.js utility.

We’ll additionally set up the dependencies we’ll be utilizing for the again finish — FaunaDB and SWR — by way of the command line. SWR (state-while-revalidate) is a Subsequent.js hook for fetching information. We’ll go into this in depth later on this tutorial.

Subsequent.js set up

To put in Subsequent.js, enter the next command within the CLI:

npx create-next-app snippetapp

The above command creates a mission listing known as snippetapp with the Subsequent.js starter template, which accommodates the required information for coding with Subsequent. When Subsequent has completed putting in, grow to be the newly created listing:

cd snippetapp

FaunaDB and SWR set up

To put in Fauna, we’ll be utilizing the next command within the CLI:

npm set up --save faunadb

Then to put in SWR:

npm set up swr@0.3.8

With this, we’ve put in all of the dependencies we’ll be utilizing to construct our utility and may now proceed to arrange our database on Fauna.

FaunaDB Setup

FaunaDB is a serverless, real-time database. It transforms a conventional database into a versatile information API that also retains the capabilities of a database and its efficiency whereas delivering safe and scalable entry to app information.

Right here, we’ll create a person account and arrange the database to retailer the information we’ll be utilizing in our snippet utility.

Making a person account

To create a person account, navigate to the Fauna signup web page and create an account.

Creating a FaunaDB account

After making a person account, you’ll be redirected to the dashboard.

The FaunaDB dashboard

Create the snippet database and assortment

Right here, we’ll create a database with the collections required to handle the code snippets of our utility. Click on on CREATE DATABASE. We’ll create a database known as snippets.

Creating a FaunaDB snippets database

On the brand new web page that opens, click on on NEW COLLECTION and create a set known as codesnippet.

collection page

After creating a set, we get a web page the place we are able to create a doc.

Create a document

Right here, you’ll click on on NEW DOCUMENT. A JSON doc will open, the place you possibly can enter the main points, as pictured beneath.

Document creation

{
  identify: "Immediate Consumer",
  description: "prompts the person",
  language: "javascript",
  code: "immediate('would you prefer to proceed')"
}

Right here, we outline a snippet with attributes: identify, description, language and code. Click on on SAVE to save lots of the brand new assortment. We’ve efficiently added a snippet to our database. Now we are able to proceed to getting our entry credentials to make use of on our app.

Secret key creation

On the dashboard, click on on Safety. This opens up a brand new web page to create our safety key.

Creating a FaunaDB security key

Right here, we’ll set the position to “server” as an alternative of “admin”, and you may give the important thing a reputation. Click on on the SAVE button to generate your key.

Making a .env file

We’ll now create a .env file inside the listing of our mission. This file will retailer our generated secret key. Within the .env file we have now this:

FAUNA_SECRET = paste your key right here

Making a Code Snippet Web page

On this part, we’ll construct the show and add web page for the snippets, and in addition add performance to it.

Open the mission listing in your code editor and navigate to the index.js file in your pages folder. Right here we’ll filter the code and begin constructing our utility:

import Head from "subsequent/head"
import Picture from "subsequent/picture"
import kinds from "../kinds/House.module.css"

export default operate House() {
  return (
    <div className={kinds.container}>
      <Head>
        <title>View Snippet</title>
        <meta identify="description" content material="Generated by create subsequent app" />
        <hyperlink rel="icon" href="/favicon.ico" />
      </Head>

      <most important className={kinds.most important}>
        <h1 className={kinds.title}>Re-usuable Code Snippets</h1>
        <p className={kinds.data}>Add your code snippets right here...</p>
        <button>Create new snippet</button>
      </most important>
    </div>
  )
}

Creating our Parts

Now we’ll create a element file that may render our snippets. Create a folder named element in your working listing and create a file named Snippets.js inside it with the next code:

import React from "react"
import kinds from "../kinds/House.module.css"

operate Snippets() {
  return (
    <div className={kinds.cont}>
      <p className={kinds.lang}>language</p>
      <h3 className={kinds.identify}>identify of snippet</h3>
      <p className={kinds.descp}>description of snippet</p>
      {}
      <div className={kinds.hyperlinks}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Importing our parts into the app

We’ll now add imports for this file in our index.js:

import Snippets from "../parts/Snippets"

And use it in our app:

<button>Create new snippet</button>
<Snippets/>

Styling our app

We are able to now model our web page. Navigate to the House.module.css file within the kinds folder and change the kinds there with the next:

.container{
  show: flex;
  top: 100%;
  min-height: 100vh;
  background: rgb(48, 48, 255);
  flex-direction: column;
  align-items: heart;
  shade: #fff;
  font-family: Montserrat;
}
.cont{
  shade: #333;
  margin-top: 5px;
  background: rgb(214, 214, 214);
  border-radius: 15px;
  padding: 10px 15px;
}
.most important button{
  width: fit-content;
  flex-grow: unset;
  show: inline-block;
  padding: 5px 10px;
  define: none;
  border: none;
  border-radius: 5%;
  font-weight: daring;
  shade: rgb(48, 48, 255);
}
.most important button:hover{
  cursor: pointer;
}
.hyperlinks{
  margin-top: 10px;
}
.hyperlinks a{
  margin-left: 5px;
}
.hyperlinks a:hover{
  cursor: pointer;
}

Viewing Our App

At this level, you must be capable of begin the dev server with npm run dev, go to http://localhost:3000, and see the skeleton of our app.

Establishing the Snippet Show Space

Subsequent, we’ll create the show part for the snippet code. Create a brand new file known as Code.js within the parts folder and import it into Snippets.js:

import React from 'react'
import kinds from '../kinds/House.module.css'
import Code from "./Code";

operate Snippets() {
  return (
    <div className={kinds.cont}>
      <p className={kinds.lang}>language</p>
      <h3 className={kinds.identify}>identify of snippet</h3>
      <p className={kinds.descp}>description of snippet</p>

      {}
      <Code />

      <div className={kinds.hyperlinks}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

For the syntax highlighting of the code, we’ll be utilizing two packages, specifically react-syntax-highlighter and react-copy-to-clipboard. We are able to obtain this by way of the CLI:

npm set up react-syntax-highlighter react-copy-to-clipboard --save

Then in Code.js:

import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/kinds/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import kinds from "../kinds/House.module.css"
operate Code() {
  const codeString = "npm set up import react from 'react'"
  const [show, setshow] = React.useState(false)
  return (
    <div>
      <button onClick={() => setshow(!present)}>Present Code</button>
      {present ? (
        <div>
          <CopyToClipboard textual content={codeString}>
            <button className={kinds.btn}>Copy</button>
          </CopyToClipboard>
          <SyntaxHighlighter language="javascript" model={atomDark}>
            {codeString}
          </SyntaxHighlighter>
        </div>
      ) : null}
    </div>
  )
}
export default Code

Right here, we created a element to show code with syntax highlighting. We’ve additionally added copy and toggle-show performance. Now within the kinds file:

.btn{
  left: 80%;
  place: relative;
}

Testing the Code Blocks

To view this alteration, you possibly can run npm run dev within the command line and look at it in your browser. We’ve the string “npm set up import react from ‘react’” displayed with syntax highlighting as a code block. There’s additionally a button to cover and show the code snippet, and a button that permits us to repeat the code from the code block.

FaunaDB Initialization

On this part, we’ll fetch information from our FaunaDB database to our app. Create a file known as Fauna.js in your mission listing:

const faunadb = require("faunadb")
const faunaClient = new faunadb.Consumer({
  secret: course of.env.FAUNA_SECRET
})
const q = faunadb.question
const getResponse = async () => {
  const { information } = await faunaClient.question(
    q.Map(
      q.Paginate(q.Paperwork(q.Assortment("codesnippet"))),
      q.Lambda("doc", q.Get(q.Var("doc")))
    )
  )
  const snippets = information.map((snippet) => {
    snippet.id = snippet.ref.id
    delete snippet.ref
    return snippet
  })
  return snippets
}

module.exports = {
  getResponse,
}

Right here, we’ve initialized FaunaDB with our secret key. We’ve additionally arrange an async request to question our assortment and return the information. We’ve saved the returned information in a variable named snippets, and deleted the ref to higher construction the information. Different functionalities for creating, updating and deleting snippets will probably be added later on this tutorial.

Be aware that, if you happen to’re getting an unauthorized error within the console, it’s possible you’ll have to specify the area identify of the goal endpoint. The default is db.fauna.com, however for the reason that introduction of Area Teams, three cloud domains can be found. Use the right area on your database’s Area Group:

  • Basic (US and EU): db.fauna.com
  • United States (US): db.us.fauna.com
  • Europe (EU): db.eu.fauna.com

Instance code:

const faunaClient = new faunadb.Consumer({
  secret: course of.env.FAUNA_SECRET,
  area: "db.eu.fauna.com"
})

Dealing with our API requests

We’ll additionally create a file to deal with our API request for our database. Inside the api folder in pages, create a file known as snippets.js with the next code:

import { getResponse } from "../../Fauna.js"
export default async operate handler(req, res) {
  console.log(req)
  if (req.technique !== "GET") {
    return res.standing(405)
  }
  attempt {
    const snippets = await getResponse()
    return res.standing(200).json(snippets)
  } catch (err) {
    console.log(err)
      res.standing(500).json({ msg: "One thing went incorrect." })
  }
}

Above, we’ve merely arrange a operate to deal with requests from our database. The snippets are returned as Json and can log errors if any happen. In Subsequent.js, any file saved within the api folder is handled as API endpoints relatively than a web page and is rendered server-side.

What’s SWR?

As stated earlier, SWR (state-while-revalidate) is a Subsequent.js hook for fetching information. It’s an ideal answer for fetching often updating information and is an effective match for our app.

Establishing SWR

We’ll use this to fetch information from FaunaDB. To make use of this, we have to import it into index.js:

import useSWR from "swr"

export default operate House() {
  const { information:snippets, mutate }=useSWR("api/snippets")
  ...
})

Right here, we’ve imported SWR and used it to fetch information as arrange in snippets.js. We’ve then saved these snippets within the snippets variable and can output them from there. We’ll now cross the snippets to our Snippets element to show:

- <Snippets />

+ {snippets &&
+   snippets.map((snippet) => (
+     <Snippets
+       key={snippet.id}
+       snippet={snippet}
+       snippetDeleted={mutate}
+     />
+   ))
+ }

Above, we’ve handed the important thing and snippet to Snippets. We’ve additionally arrange a mutate property to replace (re-fetch) snippets when a snippet is deleted. To make use of the handed information, we modify the Snippets element with the next:

operate Snippets({snippet}) {
  return (
    <div className={kinds.cont}>
      <p className={kinds.lang}>{snippet.information.language}</p>
      <h3 className={kinds.identify}>{snippet.information.identify}</h3>
      <p className={kinds.descp}>{snippet.information.description}</p>

      <Code snippet={snippet}/>

      <div className={kinds.hyperlinks}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

Above, we’ve inserted the snippet language, identify and outline obtained from FaunaDB in our code. To get the code from Fauna in our app, we’ve additionally needed to cross the snippet prop right down to the Code element.

Then within the Code element:

operate Code({snippet}){
  ...
  <div>
    <CopyToClipboard textual content={snippet.information.code}>
      <button className={kinds.btn}>Copy</button>
    </CopyToClipboard>
    <SyntaxHighlighter language="javascript" model={atomDark}>
      {snippet.information.code}
    </SyntaxHighlighter>
  </div>
  ...
}

We’re now completed with the GetSnippet performance. If we return to FaunaDB and create a brand new snippet, we see what’s pictured beneath.

added another document to FaunaDB

{
  "identify": "console.log()",
  "language": "javascript",
  "description": "logs out information",
  "code": "console.log('Good day, world!')"'
}

Working the Code

To run within the CLI:

npm run dev

In case you open the web page in your browser, you’ll have a outcome much like the picture beneath.

Snippet display page

We’ve efficiently created a snippet show web page with functionalities to point out and conceal the code and replica the code snippet.

The Snippet Add Web page

We’ll have to create a hyperlink to the add web page from our residence element. Subsequent.js has provisions that make routing simpler with out you having to put in react-router and different dependencies as you’ll if utilizing native React code.

In index.js, we’ll import the Hyperlink module from subsequent:

import Hyperlink from "subsequent/hyperlink"

Then add it to our Create new snippet button:

- <button>Create new snippet</button>

+ <Hyperlink href="https://www.sitepoint.com/add">
+   <button>Create new snippet</button>
+ </Hyperlink>

We’ll create a brand new web page in our pages folder and identify it add.js.

Again in our Fauna.js file, we’ll create and in addition export a operate to create snippets in our app:

const createSnippet = async (code, language, description, identify) => {
  return await faunaClient.question(q.Create(q.Assortment("codesnippet"), {
    information:{code, language, description, identify}
  }))
}

module.exports = {
  getResponse,
  createSnippet,
}

Right here, we’ve created the operate createSnippet, which is able to absorb some parameters and cross them as information within the new doc that will probably be created within the database.

Including a operate to create snippets

We’ll additionally configure our endpoint to create snippets. Create a brand new file known as createSnippet.js within the api folder and populate it with the next code:

import { createSnippet } from "../../Fauna"

export default async operate handler(req, res) {
  const { code, language, description, identify } = req.physique
  if (req.technique !== "POST") {
    return res.standing(405).json({msg:"unauthorized"})
  }
  attempt {
    const createdSnippet = await createSnippet(code, language, description, identify)
    return res.standing(200).json(createdSnippet)
  } catch (error) {
    console.log(error)
    res.standing(500).json({msg:"unauthorized"})
  }
}

Creating our add web page

We’ll now create the add web page in our add.js file. For our kind to create snippets, we’ll be utilizing the react-hook-form. We’ll set up this by way of the CLI:

npm set up react-hook-form

Then, in our add.js file:

import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "subsequent/router"
import model from "../kinds/kind.module.css"
import { Hyperlink } from "subsequent/hyperlink"

operate add({ snippet }) {
  const { register, handleSubmit, errors, reset } = useForm()
  const router = useRouter()
  const createSnippet = async (information) => {
    const { code, language, description, identify } = information
    console.log(information)
    attempt {
      
    } catch (error) {
      console.log(error)
    }
  }
  return (
    <div className={model.cont}>
      <kind
        className={model.kind}
        onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
      >
        <div>
          <label htmlFor="identify">Identify</label>
          <enter
            className={model.enter}
            kind="textual content"
            id="identify"
            {...register("identify", { required: true })}
          />
        </div>
        <div>
          <label className={model.label} htmlFor="language">
            language
          </label>
          <choose
            className={model.choose}
            kind="textual content"
            id="language"
            {...register("language", { required: true })}
          >
            <choice>Javascript</choice>
            <choice>Html</choice>
            <choice>CSS</choice>
          </choose>
        </div>
        <div>
          <label className={model.label} htmlFor="description">
            description
          </label>
          <textarea
            className={model.enter}
            rows={7}
            kind="textual content"
            id="description"
            placeholder="snippet description"
            {...register("description", { required: true })}
          />
        </div>
        <div>
          <label className={model.label} htmlFor="code">
            Code
          </label>
          <textarea
            className={model.enter}
            rows={8}
            columns={8}
            kind="textual content"
            id="code"
            {...register("code", { required: true })}
            placeholder="background: none;"
          />
        </div>
        <div>
          <button className={model.button}>Submit</button>
          <button className={model.button}>Cancel</button>
        </div>
      </kind>
    </div>
  )
}
export default add

Styling our Kind

Above, we’ve created our kind utilizing the react-hook-form bundle. We’ve used the handleSubmit operate useForm() within the ternary operator. Upon submission of the shape, it determines if the submission made is to create or replace an current snippet. With register, we’ve added the required property to each area in our kind. We’ve additionally added imports for a stylesheet named kind.module.css the place we have now the next kinds for our kind:

.kind {
  max-width: 800px;
  show: flex;
  justify-content: heart;
  flex-direction: column;
  align-items: heart;
}

.cont{
  background: rgb(48, 48, 255);
  top: 100%;
  min-height: 100vh;
  padding: 10px 0 0 0;
  show: flex;
  justify-content: heart;
  align-items: heart;
}

.choose,
.enter {
  show: block;
  box-sizing: border-box;
  width: 100%;
  border-radius: 4px;
  border: 1px strong black;
  padding: 10px 15px;
  margin-bottom: 15px;
  font-size: 14px;
}

.label{
  line-height: 2;
  text-align: left;
  show: block;
  margin: 5px;
  shade: white;
  font-size: 14px;
  font-weight: 200;
}

.button {
  background : #fff;
  shade: #444;
  border: none;
  border-radius: 5%;
  margin-right: 8px;
}

Creating an API endpoint to create snippets

To ship the information from our kind to the FaunaDB database, add the next code to the attempt...catch block within the createSnippet operate in add.js:

attempt {
  await fetch("/api/createSnippet", {
    technique: "POST",
    physique: JSON.stringify({ code, language, description, identify }),
    headers: {
      "Content material-type": "utility/json"
    },
  })
  router.push("https://www.sitepoint.com/")
} catch (error) {
  console.log(error)
}

Testing our Kind

Run the code and navigate to the add web page. Now if we add a brand new snippet to the shape and click on on Submit, we’ll see what’s pictured beneath.

Snippet Upload page

Once we navigate to our residence element, we are able to see the created snippet.

Created a new snippet

Making a Perform to Edit Snippets

To create our edit snippet performance, again within the Fauna.js file we’ll create and export a operate to deal with this process:

const updateSnippet = async (id, code, language, description, identify) => {
  return await faunaClient.question(q.Replace(q.Ref(q.Assortment("codesnippet"), id), {
    information: {code, language, identify, description},
  }))
}
module.exports = {
  ...
  updateSnippet,
}

Creating an API endpoint to edit snippets

This operate is much like the createSnippet operate, nevertheless it additionally takes in a parameter of id. It makes use of this ID to establish which snippets are to be edited. If the id corresponds, we replace the information with the opposite parameters. We’ll additionally create an endpoint file within the api listing known as updateSnippet.js to deal with the updates:

import { updateSnippet } from "../../Fauna"
export default async operate handler(req, res) {
  const { id, code, language, description, identify } = req.physique
  if (req.technique !== "PUT") {
    return res.standing(405).json({ msg: "unauthorized" })
  }
  attempt {
    const up to date = await updateSnippet(
      id,
      code,
      language,
      description,
      identify
    )
    return res.standing(200).json(up to date)
  }
  catch (error) {
    console.log(error)
    res.standing(500).json({ msg: "unauthorized" })
  }
}

Linking our Edit Button

Now, transfer over to the Snippets element and modify this element to utilize this operate. First, we’ll import the Hyperlink module:

...
import Hyperlink from "subsequent/hyperlink"

We additionally modify our edit button:

- <a>Edit</a>

+ <Hyperlink href={`/edit/${snippet.id}`}>
+   <a>Edit</a>
+ </Hyperlink>

Dealing with the edit snippet

When clicked, it sends a request to the web page edit with the id of the chosen snippet. Within the pages folder, create a folder named edit with a file [id].js inside it:

import { getSnippetById } from "../../Fauna"
import Add from "../add"
export default operate House({ snippet }) {
  const e-mail = ""
  const person = ""
  return (
    <div>
      <h3>Replace a snippet</h3>
      <Add snippet={snippet} e-mail={e-mail} person={person}/>
    </div>
  )
}

export async operate getServerSideProps(context) {
  attempt {
    
    const id = context.params.id
  }
  catch (error) {
    console.log(error)
    context.res.statusCode = 302
    context.res.setHeader("Location", "https://www.sitepoint.com/")
    return {props: {}}
  }
}

In [id].js, we’re passing the code snippet as props to the snippet add web page. Nonetheless, this time the add web page will comprise the information saved within the code snippet referenced by the id. To fetch the snippet by ID, we’ll have to create the getSnippetById operate within the Fauna.js file:

const getSnippetById = async (id) => {
  const snippet = await faunaClient.question(q.Get(q.Ref(q.Assortment("codesnippet"),id)))
  snippet.id = snippet.ref.id
  delete snippet.ref
  return snippet
}

module.exports = {
  getResponse,
  createSnippet,
  updateSnippet,
  getSnippetById,
}

As we export the operate, again within the [id].js file, we are able to use it to fetch a specific snippet with its ID:

attempt {
  const id = context.params.id;
  const snippet = await getSnippetById(id);
  return {
    props: { snippet },
  };
} catch (error) {
  
}

Modifying Saved Snippets

Now, within the add.js file, we’ll modify it to have the ability to entry the saved information if a snippet is to be edited:

- const { register, handleSubmit, errors, reset } = useForm()

+ const { register, handleSubmit, errors, reset } = useForm({
+   defaultValues: {
+     code: snippet ? snippet.information.code : "",
+     language: snippet ? snippet.information.language : "",
+     description: snippet ? snippet.information.description : "",
+     identify: snippet ? snippet.information.identify : "",
+   }
+ })

The code above checks if the snippet has saved information in it. If it returns true, it returns the information to the parameters: code, language, description and code. If it returns false, it returns an empty string.

Subsequent, we’ll create a operate to replace the code snippet:

const createSnippet = async (information) => { ... }

const updateSnippet = async (information) => {
  const { code, language, description, identify } = information
  const id = snippet.id
  attempt {
    await fetch("/api/updateSnippet", {
      technique: "PUT",
      physique: JSON.stringify({ code, language, description, identify, id }),
      headers: {
        "Content material-Kind": "utility/json",
      },
    })
    router.push("https://www.sitepoint.com/")
  }
  catch (error) {
    console.log(error)
  }
}

return ( ,,, )

Testing Edit Snippet Performance

If we run our code, we are able to edit the beforehand created code snippets by clicking on the Edit button, making adjustments to the information within the kind and clicking on Submit.

Including Delete Snippet Performance

Now, if we return to the House element in our browser, we’re capable of edit and replace code snippets. We are able to lastly add the ultimate performance to delete our code snippet. Create and export a brand new operate — deleteSnippet — within the Fauna.js file:

const deleteSnippet = async (id) => {
  return await faunaClient.question(q.Delete(q.Ref(q.Assortment("codesnippet"),id)))
}

module.exports = {
  ...
  deleteSnippet,
}

Creating API endpoint for the delete performance

We’ll create one other endpoint for this operate in our api folder known as deleteSnippet.js and populate it with the next code:

import { deleteSnippet } from "../../Fauna"
export default async operate handler(req, res) {
  if (req.technique !== "DELETE") {
    return res.standing(405).json({ msg: "unauthorized" })
  }
  const { id } = req.physique
  attempt {
    const deleted = await deleteSnippet(id)
    return res.standing(200).json(deleted)
  }
  catch (error) {
    console.log(error)
    res.standing(500).be a part of({ msg: "error occured" })
  }
}

Then, we modify the Snippets.js file so as to add the brand new performance:

operate Snippets({ snippet, snippetDeleted }) {
  ...
}

Then create a deleteSnippet operate to fetch the endpoint from the api and delete the snippet referenced by the ID:

operate Snippets({snippet, snippetDeleted}) {
  const deleteSnippet = async () => {
    attempt {
      await fetch("/api/deleteSnippet", {
        technique: "DELETE",
        physique: JSON.stringify({ id: snippet.id }),
        headers: {
          "Content material-Kind": "utility/json",
        },
      });
      snippetDeleted();
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div className={kinds.cont}>
      <p className={kinds.lang}>{snippet.information.language}</p>
      <h3 className={kinds.identify}>{snippet.information.identify}</h3>
      <p className={kinds.descp}>{snippet.information.description}</p>

      <Code snippet={snippet}/>

      <div className={kinds.hyperlinks}>
        <Hyperlink href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Hyperlink>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </div>
  )
}

We’ve additionally up to date the anchor factor to name the deleteSnippet operate when it’s clicked.

Testing Delete Performance

We’ve added performance to delete code snippets. We are able to now delete snippets by clicking on the Delete button in our app.

This concludes the functionalities for the snippet app. We’ll now proceed so as to add authentication measures to our app to allow solely approved customers to create or modify snippets in our app.

Consumer Authentication

Why do we want authentication? At the moment, customers can create snippets however they’ll additionally delete and modify snippets they didn’t create. We’ll want to offer a way to authorize customers to entry our web site — and so the necessity for person authentication.

We’ll set up next-auth for authentication by way of our CLI:

npm i next-auth

We’ll be utilizing a JWT token for our authentication. JWT is an ordinary used to create entry tokens for an utility.

Create a folder named auth in your api folder and inside it, create a file [...nextauth].js with the next code in it:

import NextAuth from "next-auth"
import GoogleProvider from "next-auth/suppliers/google"

export default NextAuth({
  suppliers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
    })
  ],
  jwt: {
    encryption: true
  },
  secret: course of.env.secret,
  callbacks: {
    async jwt(token, account) {
      if (account ?.accessToken) {
        token.accessToken = account.accessToken
      }
      return token
    },
    redirect: async (url, _baseUrl)=>{
      if (url === "/profile") {
        return Promise.resolve("https://www.sitepoint.com/")
      }
      return  Promise.resolve("https://www.sitepoint.com/")
    }
  }
})

After this, we’ll wrap up our parts within the _app.js file:

import '../kinds/globals.css'
import {Supplier} from "next-auth/consumer"

operate MyApp({ Element, pageProps }) {
  return (
    <Supplier session={pageProps.session}>
      <Element {...pageProps} />
    </Supplier>
  )
}
export default MyApp

Dealing with Authentication

We’ll modify our House element in index.js to return to our element if the person is authenticated, else it returns a button that results in the authentication web page:

import {signIn, signOut, useSession} from "next-auth/consumer"
...

Then inside House:

export default operate House() {
  const { information:snippets, mutate }=useSWR("api/snippets")

  const [session, loadingSession] = useSession()
  if (loadingSession) {
    <>
      <p>...authenticating</p>
    </>
  }
  ...
}

The code above checks if the app is loadingSession. If true, it returns the p tag block, else it returns the remainder of our app if there may be session. Subsequent, we’ll render the “check in” if there’s no session:

return (
  <div className={kinds.container}>
    <Head> ... </Head>

    {!session && (
      <>
        <h1>Signal in to entry snippet app</h1>
        <button onClick={() => signIn()}>Signal In</button>
      </>
    )}

    {session && (
      <>
        <most important className={kinds.most important}>
          <h3>welcome {session.person.e-mail}</h3>
          <button onClick={() => signOut()}>Signal Out</button>
          ...
        </most important>
      </>
    )}
  </div>
)

To utilize the “Google login service”, we want entry credentials from the Google cloud console. To get this, signal into your Google account and navigate to the Google Cloud console. Click on on CREATE PROJECT on the web page, enter the identify of your mission, and click on on Create.

On the brand new web page that opens, click on on + CREATE CREDENTIALS within the high menu bar and eventually choose OAuth consumer ID within the dropdown menu.

Google Cloud console

On the web page that opens, you’ll get a notification with a button asking you to “Configure Consent Display screen”. Click on on this button.

On the subsequent web page, Choose Exterior underneath the person kind and click on on Create. Enter the required fields on your “App identify” and “E mail” and click on on Save and Proceed.

Within the Scopes and Check customers sections, scroll down and click on on Save and Proceed.

Lastly, click on on Head again to dashboard and click on on the Publish button.

Now, we are able to create our key by clicking on Credentials within the aspect menu, then Create Credentials within the high menu bar. Choose Oauth Consumer ID within the dropdown and also you’ll get a web page requesting the applying kind.

Choose Net Utility then, underneath “Approved JavaScript origins”, click on Add URI and enter http://localhost. Lastly, underneath “Approved redirect URIs”, click on Add URI and enter http://localhost/api/auth/callback/google within the area, earlier than clicking on Create.

Oauth key creation

Copy the consumer ID and the consumer secret from the popup that opens and add them to the .env file:

GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret

We are able to now log in utilizing Google authentication to our app. Additionally, we’ll arrange our add.js file as a protected route in order that unauthorized customers can’t create new snippets:

import { getSession } from "next-auth/consumer"

operate Add({ snippet, person }) { ... }

export async operate getServerSideProps(context) {
  const session = await getSession(context)
  if (!session) {
    context.res.writeHead(302, { Location: "https://www.sitepoint.com/" })
    context.res.finish()
    return {}
  }
  return {
    props: {
      person: session.person,
    }
  }
}

export default Add;

Testing Consumer Authentication

If we run our app with the npm run dev command, at first we’ll get a web page requesting us to “check in”. We are able to’t navigate to the add web page by way of the /add path in our URL. We are able to solely entry our app after we use the Google login function to check in to our app.

Authorizing Customers to Create a Snippet

Lastly, we’ll modify the createSnippet performance so as to add the person e-mail to the database after which we’ll solely present the Edit and Delete buttons if the e-mail corresponds.

In Fauna.js, alter the createSnippet operate like so:

const createSnippet = async (code, language, description, identify, mail) => {
  return await faunaClient.question(q.Create(q.Assortment("codesnippet"), {
    information:{code, language, description, identify, mail}
  }))
}

Within the createSnippet.js file, make the next adjustments:

- const { code, language, description, identify } = req.physique;
+ const { code, language, description, identify, mail } = req.physique;

- const createdSnippet = await createSnippet(code, language, description, identify);
+ const createdSnippet = await createSnippet(code, language, description, identify, mail);

In add.js:

operate add({ snippet, person }) {
+  const e-mail = person.e-mail;
   ...
}

And alter the createSnippet operate and the updateSnippet operate, as follows:

const createSnippet = async (information) => {
  const { code, language, description, identify, mail } = information;
  console.log(information)
  attempt {
    await fetch("/api/createSnippet", {
      technique: "POST",
      physique: JSON.stringify({ code, language, description, identify, mail:e-mail }),
      headers: {
        "Content material-type": "utility/json"
      },
    })
    router.push("https://www.sitepoint.com/")
  } catch (error) {
    console.log(error)
  }
}

const updateSnippet = async (information) => {
  const { code, language, description, identify } = information
  const id = snippet.id
  attempt {
    await fetch("/api/updateSnippet", {
      technique: "PUT",
      physique: JSON.stringify({ code, language, description, identify, mail:e-mail }),
      headers: {
        "Content material-Kind": "utility/json",
      },
    })
    router.push("https://www.sitepoint.com/")
  }
  catch (error) {
    console.log(error)
  }
}

We are able to now proceed with making the Edit and Delete buttons solely show if the e-mail corresponds.

First, we cross the person.mail as props to the Snippet element in index.js:

<Snippets
  key={snippet.id}
  snippet={snippet}
  snippetDeleted={mutate}
+ e-mail={session.person.e-mail}
/>

Then in Snippet.js:

operate Snippets({ snippet, snippetDeleted, e-mail }) {
...
  {e-mail == snippet.information.mail && (
    <>
      <div className={kinds.hyperlinks}>
        <Hyperlink href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Hyperlink>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </>
  )}
  ...
}

Testing Our App

Run npm run dev within the CLI and open up the app in your browser. Now, if you happen to create a brand new snippet, the person e-mail is added to the database. If the e-mail doesn’t correspond, the Edit and Delete buttons aren’t proven on the snippet show web page. You may check this by logging in with a special e-mail tackle than the one used to create the code snippets.

Conclusion

We’ve lastly come to the tip of this tutorial. We’ve discovered tips on how to construct a CRUD app with Subsequent.js and FaunaDB, and tips on how to carry out CRUD operations primarily based on person authentication.

To take a look at the total code, go to the GitHub repository.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments