top of page
Writer's pictureRevanth Reddy Tondapu

Integrating Neo4j with Strapi and Displaying Data Using Next.js

Updated: Sep 26


Integrating Neo4j with Strapi and Displaying Data Using Next.js
Integrating Neo4j with Strapi and Displaying Data Using Next.js

Integrating Neo4j with Strapi and using Next.js to display the data can significantly enhance your application's data management and retrieval capabilities. This guide walks you through setting up Strapi to use PostgreSQL for user management and Neo4j for CRM data, and how to create custom APIs to access this data. Finally, we’ll use Next.js to fetch and display this data.


Step 1: Create a Strapi Project

Initialize Strapi Project

First, ensure you have Node.js and npm installed. Then, run the following command to create a new Strapi project:

npx create-strapi-app@latest neo4j-strapi --quickstart

This command will create a new Strapi project in the neo4j-strapi directory and start the Strapi server.

Install Required Packages

Navigate to your project directory:

cd neo4j-strapi

Install the necessary packages, including neo4j-driver and @neo4j/graphql:

npm install neo4j-driver @neo4j/graphql

Step 2: Configure Environment Variables

Create a .env file in the root of your Strapi project to store your database credentials securely:

DATABASE_CLIENT=postgres
DATABASE_NAME=strapi
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USERNAME=your_postgres_user
DATABASE_PASSWORD=your_postgres_password

NEO4J_URI=bolt://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=your_neo4j_password
JWT_SECRET=your_jwt_secret

Step 3: Configure PostgreSQL for User Management

Update Database Configuration

Edit the config/database.js file to use PostgreSQL for user management:

// config/database.js
const knex = require('knex');

module.exports = ({ env }) => {
  const connection = {
    client: 'postgres',
    connection: {
      host: env('DATABASE_HOST', 'localhost'),
      port: env('DATABASE_PORT', 5432),
      database: env('DATABASE_NAME', 'strapi'),
      user: env('DATABASE_USERNAME', 'strapi'),
      password: env('DATABASE_PASSWORD', 'strapi'),
      ssl: env.bool('DATABASE_SSL', false),
    },
    migrations: {
      tableName: 'knex_migrations',
    },
  };

  // Initialize Knex
  const db = knex(connection);

  // Run migrations
  db.migrate.latest()
    .then(() => console.log('Database migrated'))
    .catch(err => console.error('Database migration failed', err));

  return {
    defaultConnection: 'default',
    connections: {
      default: {
        connector: 'bookshelf',
        settings: connection.connection,
        options: {},
      },
    },
  };
};

Step 4: Integrate Neo4j GraphQL in Strapi

Load Environment Variables

First, load the environment variables using the dotenv package. Create a new file called neo4j_database.js in the config directory:

require("dotenv").config();

const neo4j = require("neo4j-driver");
const { Neo4jGraphQL } = require("@neo4j/graphql");

// Initialize Neo4j driver
const driver = neo4j.driver(
  process.env.NEO4J_URI,
  neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD)
);

// Verify connectivity
driver
  .verifyConnectivity()
  .then(() => console.log("Neo4j connection verified"))
  .catch((err) => console.error("Neo4j connection failed", err));

// Define GraphQL type definitions
const typeDefs = `
    type Company {
      uuid: ID!
      name: String!
      website: String
      company_logo_url: String
      display_name: String
      employee_count: Int
      facebook_url: String
      founded: Int
      headline: String
      industry: String
      linkedin_id: String
      linkedin_url: String
      location: String
      profiles: String
      size: String
      summary: String
      tags: String
      twitter_url: String
      type: String
    }

    type Query {
      companies: [Company] @cypher(statement: "MATCH (c:Company) RETURN c")
    }

    input CompanyCreateInput {
      uuid: ID!
      name: String!
      website: String
      company_logo_url: String
      display_name: String
      employee_count: Int
      facebook_url: String
      founded: Int
      headline: String
      industry: String
      linkedin_id: String
      linkedin_url: String
      location: String
      profiles: String
      size: String
      summary: String
      tags: String
      twitter_url: String
      type: String
    }

    input CompanyUpdateInput {
      name: String
      website: String
      company_logo_url: String
      display_name: String
      employee_count: Int
      facebook_url: String
      founded: Int
      headline: String
      industry: String
      linkedin_id: String
      linkedin_url: String
      location: String
      profiles: String
      size: String
      summary: String
      tags: String
      twitter_url: String
      type: String
    }

    type Mutation {
      createCompany(input: CompanyCreateInput!): Company @cypher(statement: "CREATE (c:Company {uuid: $input.uuid, name: $input.name, website: $input.website, company_logo_url: $input.company_logo_url, display_name: $input.display_name, employee_count: $input.employee_count, facebook_url: $input.facebook_url, founded: $input.founded, headline: $input.headline, industry: $input.industry, linkedin_id: $input.linkedin_id, linkedin_url: $input.linkedin_url, location: $input.location, profiles: $input.profiles, size: $input.size, summary: $input.summary, tags: $input.tags, twitter_url: $input.twitter_url, type: $input.type}) RETURN c")
      updateCompany(uuid: ID!, input: CompanyUpdateInput!): Company @cypher(statement: "MATCH (c:Company {uuid: $uuid}) SET c += $input RETURN c")
      deleteCompany(uuid: ID!): Company @cypher(statement: "MATCH (c:Company {uuid: $uuid}) DELETE c RETURN c")
    }
`;

// Create Neo4j GraphQL schema
const neoSchema = new Neo4jGraphQL({ typeDefs, driver });

module.exports = {
  driver,
  neoSchema,
};

Integrate Neo4j GraphQL with Strapi

Add the following code in the index.js file at the src folder in your project:

async bootstrap({ strapi }) {
  try {
    const { ApolloServer } = require("apollo-server-koa");
    const { driver, neoSchema } = require("../config/neo4j_database");

    const schema = await neoSchema.getSchema();
    const server = new ApolloServer({ schema, context: { driver } });

    await server.start();
    server.applyMiddleware({ app: strapi.server, path: "/graphql" });
  } catch (error) {
    console.error("Error during bootstrap:", error);
  }
},

Step 5: Create Custom API to Access Data from Neo4j

Create a Custom Controller

Create a new file called company.js in the api/company/controllers directory:

"use strict";
const { driver, neoSchema } = require("../../../../config/neo4j_database");
const { graphql } = require("graphql");

module.exports = {
  async getCompanies(ctx) {
    try {
      const query = `
        query {
          companies {
            company_logo_url
            display_name
            employee_count
            facebook_url
            founded
            headline
            industry
            linkedin_id
            linkedin_url
            location
            name
            profiles
            size
            summary
            tags
            twitter_url
            type
            uuid
            website
          }
        }
      `;
      const schema = await neoSchema.getSchema();
      const result = await graphql({
        schema,
        source: query,
        contextValue: { driver },
      });
      if (result.errors) {
        console.error("GraphQL Errors: ", result.errors);
        ctx.throw(500, "GraphQL Query Error", { errors: result.errors });
      }
      ctx.send(result.data);
    } catch (error) {
      console.error("Error during GraphQL query execution: ", error);
      ctx.throw(500, "Internal Server Error", { error });
    }
  },

  async createCompany(ctx) {
    try {
      const { input } = ctx.request.body;
      const session = driver.session();
      const result = await session.run(
        `
        CREATE (c:Company {
          uuid: $uuid,
          name: $name,
          website: $website,
          company_logo_url: $company_logo_url,
          display_name: $display_name,
          employee_count: $employee_count,
          facebook_url: $facebook_url,
          founded: $founded,
          headline: $headline,
          industry: $industry,
          linkedin_id: $linkedin_id,
          linkedin_url: $linkedin_url,
          location: $location,
          profiles: $profiles,
          size: $size,
          summary: $summary,
          tags: $tags,
          twitter_url: $twitter_url,
          type: $type
        })
        RETURN c
        `,
        {
          uuid: input.uuid,
          name: input.name,
          website: input.website,
          company_logo_url: input.company_logo_url,
          display_name: input.display_name,
          employee_count: input.employee_count,
          facebook_url: input.facebook_url,
          founded: input.founded,
          headline: input.headline,
          industry: input.industry,
          linkedin_id: input.linkedin_id,
          linkedin_url: input.linkedin_url,
          location: input.location,
          profiles: input.profiles,
          size: input.size,
          summary: input.summary,
          tags: input.tags,
          twitter_url: input.twitter_url,
          type: input.type
        }
      );
      ctx.send(result.records[0].get('c').properties);
    } catch (error) {
      console.error("Error during Neo4j node creation: ", error);
      ctx.throw(500, "Internal Server Error", { error });
    } finally {
      session.close();
    }
  },

  async updateCompany(ctx) {
    try {
      const { uuid, input } = ctx.request.body;
      const session = driver.session();
      const result = await session.run(
        `
        MATCH (c:Company { uuid: $uuid })
        SET c += {
          name: $name,
          website: $website,
          company_logo_url: $company_logo_url,
          display_name: $display_name,
          employee_count: $employee_count,
          facebook_url: $facebook_url,
          founded: $founded,
          headline: $headline,
          industry: $industry,
          linkedin_id: $linkedin_id,
          linkedin_url: $linkedin_url,
          location: $location,
          profiles: $profiles,
          size: $size,
          summary: $summary,
          tags: $tags,
          twitter_url: $twitter_url,
          type: $type
        }
        RETURN c
        `,
        {
          uuid: uuid,
          name: input.name,
          website: input.website,
          company_logo_url: input.company_logo_url,
          display_name: input.display_name,
          employee_count: input.employee_count,
          facebook_url: input.facebook_url,
          founded: input.founded,
          headline: input.headline,
          industry: input.industry,
          linkedin_id: input.linkedin_id,
          linkedin_url: input.linkedin_url,
          location: input.location,
          profiles: input.profiles,
          size: input.size,
          summary: input.summary,
          tags: input.tags,
          twitter_url: input.twitter_url,
          type: input.type
        }
      );
      ctx.send(result.records[0].get('c').properties);
    } catch (error) {
      console.error("Error during Neo4j node update: ", error);
      ctx.throw(500, "Internal Server Error", { error });
    } finally {
      session.close();
    }
  },

  async deleteCompany(ctx) {
    try {
      const { uuid } = ctx.params;
      const session = driver.session();
      await session.run(
        `
        MATCH (c:Company { uuid: $uuid })
        DELETE c
        `,
        { uuid: uuid }
      );
      ctx.send({ message: "Company deleted" });
    } catch (error) {
      console.error("Error during Neo4j node deletion: ", error);
      ctx.throw(500, "Internal Server Error", { error });
    } finally {
      session.close();
    }
  }
}

Define Routes

Extend the routes.json file in the api/company/config directory:

{
  "routes": [
    {
      "method": "GET",
      "path": "/companies",
      "handler": "company.getCompanies",
      "config": {
        "policies": []
      }
    },
    {
      "method": "POST",
      "path": "/companies",
      "handler": "company.createCompany",
      "config": {
        "policies": []
      }
    },
    {
      "method": "PUT",
      "path": "/companies/:uuid",
      "handler": "company.updateCompany",
      "config": {
        "policies": []
      }
    },
    {
      "method": "DELETE",
      "path": "/companies/:uuid",
      "handler": "company.deleteCompany",
      "config": {
        "policies": []
      }
    }
  ]
}

Step 6: Custom API permission for the user and build and run


Build Your Project

Build your project with:

npm run build

Start the server:

npm run develop

Step 7: Set Up Next.js Project

Create a New Next.js Application

Navigate to the root of your project and create a new Next.js app:

npx create-next-app client
cd client

Fetch Data in Next.js Pages

Create pages to fetch, create, update, and delete data from the custom API.

Create a Companies Page

Create a new file called companies.js in the pages directory:

// client/pages/companies.js
import { useEffect, useState } from 'react';

const Companies = () => {
  const [companies, setCompanies] = useState([]);
  const [formData, setFormData] = useState({ name: '', website: '' });
  const [editingCompany, setEditingCompany] = useState(null);

  useEffect(() => {
    fetchCompanies();
  }, []);

  const fetchCompanies = async () => {
    const response = await fetch('http://localhost:1337/companies');
    const data = await response.json();
    setCompanies(data.companies);
  };

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  };

  const handleFormSubmit = async (e) => {
    e.preventDefault();
    if (editingCompany) {
      await updateCompany(editingCompany.uuid, formData);
    } else {
      await createCompany(formData);
    }
    setFormData({ name: '', website: '' });
    setEditingCompany(null);
    fetchCompanies();
  };

  const createCompany = async (input) => {
    await fetch('http://localhost:1337/companies', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ input }),
    });
  };

  const updateCompany = async (uuid, input) => {
    await fetch(`http://localhost:1337/companies/${uuid}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ input }),
    });
  };

  const deleteCompany = async (uuid) => {
    await fetch(`http://localhost:1337/companies/${uuid}`, {
      method: 'DELETE',
    });
    fetchCompanies();
  };

  const handleEditClick = (company) => {
    setFormData({ name: company.name, website: company.website });
    setEditingCompany(company);
  };

  return (
    <div>
      <h1>Companies</h1>
      <form onSubmit={handleFormSubmit}>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleInputChange}
          placeholder="Company Name"
          required
        />
        <input
          type="text"
          name="website"
          value={formData.website}
          onChange={handleInputChange}
          placeholder="Company Website"
          required
        />
        <button type="submit">{editingCompany ? 'Update' : 'Create'}</button>
      </form>
      <ul>
        {companies.map((company) => (
          <li key={company.uuid}>
            {company.name} - {company.website}
            <button onClick={() => handleEditClick(company)}>Edit</button>
            <button onClick={() => deleteCompany(company.uuid)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Companies;

Step 8: Run Your Applications

Run Strapi

Navigate to the root of your Strapi project and run the Strapi server:

npm run develop

Run Next.js

Navigate to the client directory and start the Next.js development server:

npm run dev

Open your browser and navigate to http://localhost:3000/companies to see the list of companies fetched from the Neo4j database via the custom API, and use the form to create, update, and delete companies.


Conclusion

By following this step-by-step guide, you have successfully set up a robust system that integrates Strapi with a Neo4j database for CRM data management and uses PostgreSQL for user management. You also created custom APIs in Strapi to access this data and displayed it using a Next.js frontend application.


Key Takeaways

  • Strapi Setup: You configured Strapi to handle different databases for user management and CRM data, enabling a more modular and scalable architecture.

  • Neo4j Integration: By integrating Neo4j with Strapi using the Neo4j GraphQL library, you leveraged the powerful graph database capabilities to handle complex relationships and queries.

  • Custom APIs: Creating custom controllers and routes in Strapi allowed you to expose specific data endpoints, giving you fine-grained control over your API.

  • Next.js Frontend: Using Next.js to fetch and display data from your Strapi API provided a modern, efficient way to build and render your web application.


Future Enhancements

  • Advanced Queries: You can extend the GraphQL schema to include more complex queries and mutations, allowing for richer interactions with your Neo4j data.

  • Caching and Optimization: Implement caching strategies in both Strapi and Next.js to improve performance, especially for frequently accessed data.

  • Real-Time Updates: Integrate WebSockets or other real-time technologies to push updates from Neo4j to your Next.js frontend, providing live data updates.

  • User Roles and Permissions: Enhance the authentication middleware to support different user roles and permissions, providing more granular access control.

  • Extended CRUD Operations: Implement more sophisticated CRUD operations with validations, error handling, and data transformations to ensure data integrity and consistency.


Final Thoughts

This setup not only offers a powerful backend to manage and query complex data relationships but also provides a modern frontend to present this data to users effectively. The separation of concerns between user management and CRM data ensures that your application remains scalable and maintainable as it grows.

By leveraging the strengths of Strapi, Neo4j, and Next.js, you have created a flexible and powerful architecture capable of handling a wide range of business requirements. Whether you're building a CRM, a social network, or any other data-intensive application, this stack provides a solid foundation to build upon.

Thank you for following along with this guide. We hope it has been informative and helpful in setting up your project. Happy coding!

77 views0 comments

Comments


bottom of page