Building Graph Database Applications with Apache AGE and Node.js: A Step-by-Step Tutorial
Tags: apacheage, postgres, node, javascript
Published: 2023-06-22 20:47:00

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](https://dev.to/omarsaad/step-by-step-guide-to-install-postgresql-apache-age-on-windows-28gf). 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](https://nodejs.org/en)

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:

```bash
node --version
```

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
```

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
```

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
```

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.

```json
"express": "^4.17.1",
"pg": "^8.5.1",
"dotenv": "^8.2.0"
```

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}`);
});

```

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
}
```

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}`);
        }
    }
}
```

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}`);
});
```

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
```
The body of the POST request should be in the following format:
```
"name" : "Iphone 14",
"price": 1000,
"category" : "Mobile phones"
```

If we want to retrieve all the products than we should send the following request:
```
GET localhost:3000/products
```
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](https://age.apache.org/age-manual/master/index.html)
[Apache AGE Github](https://github.com/apache/age)

**Contribute to Apache AGE**

[Apache AGE Github](https://github.com/apache/age)
[Apache AGE Viewer Github](https://github.com/apache/age-viewer)