Using Neo4j in Your Next Next.js Project

After watching some shiny new videos from Next.js Conf 2022, I thought I’d take a closer look at Next.js and see how the framework can help me build my next Neo4j-based web application.

Basically, adding Neo4j integration to a Next.js project is the same as any other Node.js/TypeScript-based project. However, the different data fetching methods and rendering both server-side and client-side pose some interesting challenges.

Let’s take a look at how we can use Neo4j in the Next.js project.

What is Next.js?

Next.js is a React-based framework that provides a starting point for building web applications. The framework provides the building blocks for many common features that developers need to consider when building modern applications such as UI components, data fetching and rendering.

The framework also focuses on performance, providing the ability to pre-generate static HTML pages using Static-Site Generation (SSG)Render HTML to server using request times out Server-Side Rendering (SSR) And also render React components client-side using Client-Side Rendering (CSR),

You can read more about Next.js here.

What is Neo4j?

Chances are, if you found this article via search, you would know more about Next.js than Neo4j. Neo4j is a graph databasea database in which nodes – who represent the entities or thingsConnect and bond together.

Neo4j comes into its own when working with highly connected datasets or as an alternative to complex relational database schemas where multiple joins are required. The golden rule is that if your queries involve three or more joins, you really should be using a graph database.

You can read more about Neo4j here.

Why Neo4j and Next.js?

Next.js is gaining momentum as one of the most popular frameworks for building modern web applications. The advantage of using Next.js is that your front-end and back-end code are all self-contained in the same subfolder api/ directory.

If you’re building a Neo4j-supported project, it’s relatively simple to create an integration with the Neo4j JavaScript driver. All you have to do is create a new instance of the driver within the application, then use the driver to execute the cipher statement and get the result.

Of course, you can use the Neo4j JavaScript driver directly from the React components, but this means exposing the database credentials through the client which can be a security risk. Instead, if you need on-demand data from Neo4j in client-side rendering, you can create an API handler to execute Cypher statements server-side and return the result.

Creating a Free Neo4j AuraDB Instance

Neo4j AuraDB, Neo4j’s fully managed cloud service provides a auraDB free Example for all users, completely free and no credit card required.

If you sign in or register for Neo4j Aura on Cloud.neo4j.io, you will see a new instance button at the top of the screen. If you click on this button, you will be able to choose between an empty database or a database already filled with sample data.

For this article, I suggest choosing Graph-based Recommendations Dataset, which includes movie, actor, director and user ratings. This dataset is a good introduction to graph concepts and can be used to build movie recommendation algorithms. We use it at GraphAcademy, including building Neo4j applications with the Node.js curriculum.

Click to create To make your example. Once this is done, a modal window will appear with the generated password.

aura credentials

press download button to download your credentials, we’ll need these a little later. After a few minutes, your example will be ready to explore. you can click to explore Click the button to explore the graph with Neo4j Bloom, or to query the graph using Cypher query tab,

Neo4j AuraDB Instance

You can check it in your own time, for now, let’s focus on our Next.js application.

Creating a new Next.js project

You can create a new Next.js project from a template using the CREATE NEXT APP CLI command.

npx create-next-app@latest
enter fullscreen mode

exit fullscreen mode

The command will prompt you for the name of the project and any dependencies to be installed.

Adding Neo4j Helper Function

To install the Neo4j JavaScript driver, first install the dependencies:

npm install --save neo4j-driver
# or yarn add neo4j-driver
enter fullscreen mode

exit fullscreen mode

Next.js comes with built-in support for environment variables, so we can easily copy the credential file downloaded from the above Neo4j Aura console, rename it .env and put in directory root.

Then we can access those variables via process.env Variables:

const { NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD } = process.env
enter fullscreen mode

exit fullscreen mode

Next, create a new folder named lib/ and then create a new neo4j.js file. would you like to import neo4j object from neo4j-driver Use the above credentials to create dependency and driver instances

// lib/neo4j.js
const driver = neo4j.driver(
  process.env.NEO4J_URI,
  neo4j.auth.basic(
    process.env.NEO4J_USERNAME,
    process.env.NEO4J_PASSWORD
  )
)
enter fullscreen mode

exit fullscreen mode

When executing a Cypher statement against a Neo4j instance, you need to open a session and execute the statement within a read or write transaction. This can get a bit cumbersome after a while, so instead, I recommend writing helper functions for reading and writing queries:

// lib/neo4j.js
export async function read(cypher, params = {}) {
  // 1. Open a session
  const session = driver.session()

  try {
    // 2. Execute a Cypher Statement
    const res = await session.executeRead(tx => tx.run(cypher, params))

    // 3. Process the Results
    const values = res.records.map(record => record.toObject())

    return values
  }
  finally {
    // 4. Close the session 
    await session.close()
  }
}

export async function write(cypher, params = {}) {
  // 1. Open a session
  const session = driver.session()

  try {
    // 2. Execute a Cypher Statement
    const res = await session.executeWrite(tx => tx.run(cypher, params))

    // 3. Process the Results
    const values = res.records.map(record => record.toObject())

    return values
  }
  finally {
    // 4. Close the session 
    await session.close()
  }
}
enter fullscreen mode

exit fullscreen mode

If you want to go deeper into this code or best practices, I suggest you check out the Neo4j and Node.js courses on GraphAcademy.

Now that we have a way to query Neo4j, let’s look at the options for data fetching in Next.js.

Data Fetching in Next.js

Next.js allows rendering of content in several ways.

  1. Static-Site Generation (SSG) – where static HTML pages are generated Make Time
  2. Server-Side Rendering (SSR) – HTML is generated server-side when the request comes in
  3. Client-Side Rendering (CSR) – HTTP requests are executed in the browser with JavaScript and the response updates the DOM

Depending on your use case, you may need a mix of these methods. Assuming you’re running a movie recommendation site, it might make sense to use SSG to create a marketing page. Movie information is kept in a database and changes regularly, so these pages must be served by the server using SSR. When a user comes in to rate a movie, the interaction should happen through an API request and a result provided using CSR.

Let’s take a look at the implementation of each of these records.

static page generation

For example, let’s say that normal style pages won’t change often and won’t require any user interaction. By generating static pages, we can serve cached versions of pages and take the load off the server.

any component in pages/ directory that exports a getStaticProps() The function (known as a page) will be generated at build time and will act as a static file.

Components created in the Pages folder will automatically be mapped to a route. To create a page that will be available here /genres you have to make one pages/genres/index.jsx file. The component needs to be exported a default function that returns a JSX component, and a getStaticProps() Celebration.

First, to get the data required by the component, create getStaticProps() function and call this cipher statement a . execute in Reading transactions.

// pages/genres/index.jsx
export async function getStaticProps() {
  const res = await read(`
    MATCH (g:Genre)
    WHERE g.name <> '(no genres listed)'

    CALL {
    WITH g
    MATCH (g)<-[:IN_GENRE]-(m:Movie)
    WHERE m.imdbRating IS NOT NULL AND m.poster IS NOT NULL
    RETURN m.poster AS poster
    ORDER BY m.imdbRating DESC LIMIT 1
    }

    RETURN g {
      .*,
      movies: toString(size((g)<-[:IN_GENRE]-(:Movie))),
      poster: poster
    } AS genre
    ORDER BY g.name ASC
  `)

  const genres = res.map(row => row.genre)

  return {
    props: {
      genres,
    }
  }
}
enter fullscreen mode

exit fullscreen mode

anything came back props The default from this function will be passed into the component as a prop.

Now, export a default function that displays a list of styles.

// pages/genres/index.jsx
export default function GenresList({ genres }) {
  return (
    <div>
      <h1>Genres</h1>

      <ul>
        {genres.map(genre => <li key={genre.name}>
          <Link href={`/genres/${genre.name}`}>{genre.name} ({genre.movies})</Link>
        </li>)}
      </ul>
    </div>
  )
}
enter fullscreen mode

exit fullscreen mode

This should generate an unordered list of links for each style:
style list

Looking good…

if you drive npm run build order, you will see genres.html inside the file .next/server/pages/ directory.

Using Neo4j for Server-Side Rendering

The movie list on each genre page may change frequently, or you may want to add additional interactions to the page. In this case, it makes sense to render this page on the server. By default, Next.js will cache this page for a short period of time which is perfect for websites with a high amount of traffic.

Each style link on the previous page. links to /genres/[name] – For example /genres/Action, a. by making pages/genres/[name].jsx file, Next.js knows to automatically listen for requests that begin with any URL /genres/ and find anything after the slash a name url parameter.

it can be accessed by getServerSideProps() Function, which will instruct Next.js to render this page using server-side rendering when the request arrives.

getServerSideProps() The function should be used to get the data needed to render the page and return it in props key.

export async function getServerSideProps({ query, params }) {
  const limit = 10
  const page = parseInt(query.page ?? '1')
  const skip = (page - 1) * limit

  const res = await read(`
    MATCH (g:Genre {name: $genre})
    WITH g, size((g)<-[:IN_GENRE]-()) AS count

    MATCH (m:Movie)-[:IN_GENRE]->(g)
    RETURN
      g { .* } AS genre,
      toString(count) AS count,
      m {
        .tmdbId,
        .title
      } AS movie
    ORDER BY m.title ASC
    SKIP $skip
    LIMIT $limit
  `, {
    genre: params.name,
    limit: int(limit),
    skip: int(((query.page || 1)-1) * limit)
  })

  const genre = res[0].genre
  const count = res[0].count

  return {
    props: {
      genre,
      count,
      movies: res.map(record => record.movie),
      page, skip, limit,
    }
  }
}
enter fullscreen mode

exit fullscreen mode

In the example above, I get the name of the movie the . resembles params object in request context which is just passed as argument getServerSideProps() Celebration. i also try to get ?page= Query parameter from URL to provide paged list of movies.

These values ​​will be passed as props again omission function, and therefore can be used to list movies and pagination links.

export default function GenreDetails({ genre, count, movies, page, skip, limit }) {
  return (
    <div>
      <h1>{genre.name}</h1>
      <p>There are {count} movies listed as {genre.name}.</p>


      <ul>
        {movies.map(movie => <li key={movie.tmdbId}>{movie.title}</li>)}
      </ul>

      <p>
        Showing page #{page}. <br />
        {page > 1 ? <Link href={`/genres/${genre.name}?page=${page-1}`}> Previous</Link> : ' '}
        {' '}
        {skip + limit < count ? <Link href={`/genres/${genre.name}?page=${page+1}`}>Next</Link> : ' '}
      </p>

    </div>
  )
}
enter fullscreen mode

exit fullscreen mode

Next.js then renders a list of movies with each request.

A list of films in the adventure genre

Using Neo4j for Client-Side Data Fetching

As it stands, for each click of the previous and next links above, the entire page will reload which is not ideal. While this is just a small example so far, reloading KB’s worth of HTML to render the header and footer means extra load on the server and more data sent over the wire.

Instead, you can create a React component that will asynchronously load a list of movies via a client-side HTTP request. This would mean that the list of movies can be updated without reloading the entire page, giving a smoother viewing experience to the end user.

To support this, we need to create an API route that will return a list of movies as JSON.

any file in pages/api/ The directory is treated as a route handler, a default exported function that accepts request and response parameters, and is expected to return an HTTP status and response.

So to make an API route to serve the list of movies http://locahost:3000/api/movies/[name]/moviescreate a new movies.js file in pages/api/genres/[name] folder.

// pages/api/genres/[name]/movies.js
export default async function handler(req, res) {
  const { name } = req.query
  const limit = 10
  const page = parseInt(req.query.page as string ?? '1')
  const skip = (page - 1) * limit

  const result = await read<MovieResult>(`
    MATCH (m:Movie)-[:IN_GENRE]->(g:Genre {name: $genre})
    RETURN
      g { .* } AS genre,
      toString(size((g)<-[:IN_GENRE]-())) AS count,
      m {
        .tmdbId,
        .title
      } AS movie
    ORDER BY m.title ASC
    SKIP $skip
    LIMIT $limit
  `, {
      genre: name,
      limit: int(limit),
      skip: int(skip)
  })

  res.status(200).json({
    total: parseInt(result[0]?.count) || 0,
    data: result.map(record => record.movie)
  })
}
enter fullscreen mode

exit fullscreen mode

The above function executes a cipher statement in a read transaction, processes the results and returns a list of
Movies as a JSON response.

A quick GET request to http://localhost:3000/api/genres/Action/movies shows a list of movies:

[
  {
    "tmdbId": "72867",
    "title": "'Hellboy': The Seeds of Creation"
  },
  {
    "tmdbId": "58857",
    "title": "13 Assassins (JΓ»san-nin no shikaku)"
  },
  /* ... */
]
enter fullscreen mode

exit fullscreen mode

This API handler is called through a React component in a . can be called in useEffect hook.

// components/genre/movie-list.tsx
export default function GenreMovieList({ genre }: GenreMovieListProps) {
  const [page, setPage] = useState<number>(1)
  const [limit, setLimit] = useState<number>(10)
  const [movies, setMovies] = useState<Movie[]>()
  const [total, setTotal] = useState<number>()

  // Get data from the API
  useEffect(() => {
    fetch(`/api/genres/${genre.name}/movies?page=${page}&limit=${limit}`)
      .then(res => res.json())
      .then(json => {
        setMovies(json.data)
        setTotal(json.total)
      })


  }, [genre, page, limit])


  // Loading State
  if (!movies || !total) {
    return <div>Loading...</div>
  }

  return (
    <div>
      <ul>
        {movies.map(movie => <li key={movie.tmdbId}>{movie.title}</li>)}
      </ul>

      <p>Showing page {page}</p>

      {page > 1 && <button onClick={() => setPage(page - 1)}>Previous</button>}
      {page * limit < total && <button onClick={() => setPage(page + 1)}>Next</button>}
    </div>
  )
}
enter fullscreen mode

exit fullscreen mode

The component is then in charge of pagination and any update to the list does not re-render the entire page.

conclusion

This is far from a comprehensive guide to Next.js or Neo4j integration, but hopefully, it serves as a quick reference for anyone who is looking to integrate Neo4j, or any other database for that matter, into one. Best way to integrate with Next.js application.

All the code for this experiment is available on Github.

If you are interested in learning more about Next.js, they have designed a course for developers to learn the basics.

If you want to learn more about Neo4j, I would recommend you to take a look at the Beginner Neo4j course on GraphAcademy. If you want to learn more about how to use the Neo4j JavaScript driver in a Node.js or TypeScript project, I would also recommend the Creating Neo4j Applications with Node.js course.

If you have any comments or questions, feel free to contact me on twitter,

Leave a Comment