DEV Community

Omar Saad
Omar Saad

Posted on

Building Graph Database Applications with Apache AGE and Node.js: A Step-by-Step Tutorial

In this tutorial we will introduce how to use Apache AGE with your NodeJs application.

What is Apache AGE ?

Apache AGE is a graph database system that is built on top of PostgreSQL. It allows users to store, manage, and analyze large-scale graph data in a highly performant and scalable way.

Apache AGE combines the benefits of a powerful relational database management system (PostgreSQL) with the flexibility and scalability of a graph database. This means that users can store their data in a relational format and also perform graph-based queries and analyses on the data.

Prerequisites

  1. PostgreSQL Version 11 or 12: Make sure you have PostgreSQL installed on your machine. Apache AGE relies on PostgreSQL, so it's important to have either version 11 or 12.

  2. Apache AGE Installation and Configuration: Install and configure Apache AGE to seamlessly integrate with your PostgreSQL installation. If you haven't installed PostgreSQL and Apache AGE yet, we recommend following our comprehensive step-by-step guide for easy installation on Windows. You can access the tutorial here. This guide will walk you through the entire process and ensure a smooth setup.

  3. Node.js: Ensure that Node.js is installed on your system. You can download and install the latest version of Node.js from the official website: Node.js

Having Node.js installed will provide the necessary runtime environment for running your Node.js applications and executing JavaScript code.

Verify the installation by opening a terminal or command prompt and running the following command:

node --version
Enter fullscreen mode Exit fullscreen mode

You should see the version number of Node.js printed in the terminal if it is installed correctly.

If Node.js is not installed, please download and install it using the appropriate installer for your operating system.

Now that you have Node.js installed, you're ready to proceed with the tutorial and explore the integration of Apache AGE with Node.js and PostgreSQL.

Apache AGE with NodeJs

1. To initialize a new Node.js project, you can run the npm init command in your project directory. This command will prompt you with a series of questions to gather information about your project and generate a package.json file. The package.json file is essential for managing your project's dependencies and scripts.

To run npm init, open your terminal or command prompt, navigate to your project directory, and execute the following command:

npm init
Enter fullscreen mode Exit fullscreen mode

You will be prompted to enter various details such as the project name, version, description, entry point, test command, repository, and more. Feel free to provide the relevant information according to your project requirements. If you wish to skip any prompts, you can add the -y flag to automatically generate the package.json file with default values:

npm init -y
Enter fullscreen mode Exit fullscreen mode

Once you have completed the npm init process, a package.json file will be created in your project directory, which will serve as a configuration file for your Node.js project.

2.To configure your application's environment variables, you'll need to create a .env file. This file will store the necessary variables for your application to connect to the PostgreSQL database. Here's an example of how the .env file should be structured:

POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432
POSTGRES_DB=your_db_name
POSTGRES_USER=your_user_name
POSTGRES_PASSWORD=your_password
Enter fullscreen mode Exit fullscreen mode

Replace your_db_name, your_user_name, and your_password with the actual values specific to your PostgreSQL setup. These variables specify the host, port, database name, username, and password required for establishing a connection between your Node.js application and the PostgreSQL database.

3.Include the following dependencies in package.json file.

"express": "^4.17.1",
"pg": "^8.5.1",
"dotenv": "^8.2.0"
Enter fullscreen mode Exit fullscreen mode

Ensure that the formatting of your package.json file remains intact.

Once you have added the dependencies, save the changes to your package.json file. Your Node.js application will now be able to utilize these dependencies.

4. Create server.js file in your main directory. This file will be the entry point of your application.

const express = require('express');
require('dotenv').config();

const app = express();
const port = process.env.PORT || 3000;

// Define your routes and middleware here
//TODO

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Enter fullscreen mode Exit fullscreen mode

5. Create a file named database.js in your project's directory. This file will handle the database connection settings.

import { Pool } from 'pg';
import dotenv from 'dotenv';

dotenv.config();

const POSTGRES_HOST = process.env.POSTGRES_HOST;
const POSTGRES_PORT = process.env.POSTGRES_PORT;
const POSTGRES_DB = process.env.POSTGRES_DB;
const POSTGRES_USER = process.env.POSTGRES_USER;
const POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD;

const client = new Pool({
  host: POSTGRES_HOST,
  port: POSTGRES_PORT,
  database: POSTGRES_DB,
  user: POSTGRES_USER,
  password: POSTGRES_PASSWORD,
  // If local, set ssl to false to avoid errors
  // ssl: true,
});
async function initAGE() {
    const sql = `CREATE EXTENSION IF NOT EXISTS age;
    LOAD 'age';
    client.query(sql);
    SET search_path to ag_catalog,"$user",public; `;
    try{
    const conn = await client.connect();
    await conn.query(sql);
    conn.release();
    } catch (err) {
        throw new Error(`Could not init agent. Error: ${err}`);
    }
}


export default {
    initAGE,
    client
}
Enter fullscreen mode Exit fullscreen mode

6. Create product_model.js file in which we will implement the basic CRUD operations for our products application.

import client from "./database";


export class ProductStore {
    GRAPH_NAME = 'products';
    async getAllProducts() {
        try {
            const conn = await client.connect();
            const sql = `SELECT * FROM 
            cypher('${GRAPH_NAME}', $$
                MATCH (p:Product) RETURN p
                $$)
            as (result agtype)`;
            const result = await conn.query(sql);
            conn.release();
            return result.rows;
        } catch (err) {
            throw new Error(`Could not get products. Error: ${err}`);
        }
    }

    async createProduct(name,price,category) {
        try{
            const conn = await client.connect();
            const sql = `SELECT * FROM 
            cypher('${GRAPH_NAME}', $$
                CREATE (p:Product {name: '${name}', price: ${price}, category: '${category}'}) RETURN p
                $$)
            as (result agtype)`;
            const result = await conn.query(sql);
            conn.release();
            return result.rows;

        } catch (err) {
            throw new Error(`Could not add new product ${product.name}. Error: ${err}`);
        }
    }

    async getProductById(id) {
        try {
            const conn = await client.connect();
            const sql = `SELECT * FROM 
            cypher('${GRAPH_NAME}', $$
                MATCH (p:Product) WHERE id(p) = ${id} RETURN p
                $$)
            as (result agtype)`;
            const result = await conn.query(sql);
            conn.release();
            return result.rows;
        } catch (err) {
            throw new Error(`Could not get product ${id}. Error: ${err}`);
        }
    }

    async updateProduct(id, name, price, category) {
        try {
            const conn = await client.connect();
            const sql = `SELECT * FROM 
            cypher('${GRAPH_NAME}', $$
                MATCH (p:Product) WHERE id(p) = ${id} SET p.name = '${name}', p.price = ${price}, p.category = '${category}' RETURN p
                $$)
            as (result agtype)`;
            const result = await conn.query(sql);
            conn.release();
            return result.rows;
        } catch (err) {
            throw new Error(`Could not update product ${id}. Error: ${err}`);
        }
    }

    async deleteProduct(id) {
        try {
            const conn = await client.connect();
            const sql = `SELECT * FROM 
            cypher('${GRAPH_NAME}', $$
                MATCH (p:Product) WHERE id(p) = ${id} DELETE p
                $$)
            as (result agtype)`;
            const result = await conn.query(sql);
            conn.release();
            return result.rows;
        } catch (err) {
            throw new Error(`Could not delete product ${id}. Error: ${err}`);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

7. We will edit our server.js file to add products routes.
For this example we will only implement GET and POST routes.
The updated server.js file:

const express = require('express');
require('dotenv').config();


const app = express();
const port = process.env.PORT || 3000;

// Define your routes and middleware here
const initAGE = require('./database').initAGE;
// Load AGE extension
initAGE();
app.get('/products', (req, res) => {
    try{
    const productModel = require('./product_model');
    const products = productModel.getProducts();
    res.send({
        message : 'Products retrieved successfully',
        data : products
    });
    return;
    } catch (err) {
        res.send({
            message : `Could not get products. Error: ${err}`,
            data : []
        });
        return;
    }

});

app.post('/products', (req, res) => {
    try{
    const productModel = require('./product_model');
    const products = productModel.createProduct(req.body.name, req.body.price, req.body.category);
    res.send({
        message : 'Products created successfully',
        data : products
    });
    return;
    } catch (err) {
        res.send({
            message : `Could not create products. Error: ${err}`,
            data : []
        });
        return;
    }

});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

8. Run npm install to install the project dependencies.

9. Run npm run start to start nodejs server.

10. Now you can test the server using postman or any other tools by making the following requests:

POST localhost:3000/products
Enter fullscreen mode Exit fullscreen mode

The body of the POST request should be in the following format:

"name" : "Iphone 14",
"price": 1000,
"category" : "Mobile phones"
Enter fullscreen mode Exit fullscreen mode

If we want to retrieve all the products than we should send the following request:

GET localhost:3000/products
Enter fullscreen mode Exit fullscreen mode

This request should return all the products in our database.

Congratulations on successfully building your Node.js application integrated with PostgreSQL and Apache AGE! You've created a powerful combination that enables efficient data management and advanced graph-based querying. Well done!

In the upcoming tutorials, we will delve into building more complex applications to further deepen our understanding. We will explore advanced features, demonstrate practical use cases, and tackle more intricate scenarios. By expanding our knowledge and exploring these advanced concepts, we will be able to leverage the full potential of integrating Apache AGE.

References
Apache AGE documentation
Apache AGE Github

Contribute to Apache AGE

Apache AGE Github
Apache AGE Viewer Github

Top comments (1)

Collapse
 
reallyvirtual profile image
Sohaib Athar

client.query(sql); in the first database.js block between SQL query seems misplaced.