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!
Comments