Integrating GraphQL APIs in Node-RED

GraphQL is transforming the way APIs are designed. Unlike traditional REST APIs, which often require multiple requests to different endpoints, GraphQL provides a single, flexible endpoint that allows you to fetch exactly the data you need—nothing more, nothing less. In this article, you will learn how to integrate GraphQL with Node-RED and build APIs that efficiently serve your application's data requirements.

Getting Started

First, you'll need to install the GraphQL package for Node-RED. This adds the essential nodes for working with GraphQL endpoints.

Installation Steps:

  1. Open Node-RED and navigate to Menu → Manage palette
  2. Go to the Install tab
  3. Search for node-red-contrib-graphql and install it

Setting Up Your GraphQL Connection

Once installed, you'll configure your first GraphQL endpoint. This is where you define how Node-RED connects to your GraphQL server.

Configuration Process:

  1. Drag a graphql node onto your canvas
  2. Double-click to open the configuration panel
  3. Click the pencil icon next to Endpoint to create a new configuration
  4. Fill in these essential settings:
    • Name: Use a descriptive name like "User Management API" or "Countries Database"
    • Endpoint: Your GraphQL server URL (e.g., https://api.example.com/graphql)
    • Token: Add authentication if required (Bearer tokens are the most common)

Important: For sensitive credentials such as tokens, use environment variables to prevent them from being exposed when sharing flows. Learn more about using environment variables in Node-RED here.

Understanding GraphQL Query Structure

GraphQL queries are intuitive once you understand the basic pattern. You're essentially describing the shape of the data you want to receive.

Basic Query Example:

query {
countries {
code
name
capital
}
}

This query says: "Get me a list of countries, but only return the code, name, and capital for each one." The server won't send population, area, or any other fields—just what you requested.

Response Structure:

{
"data": {
"countries": [
{
"code": "US",
"name": "United States",
"capital": "Washington D.C."
}
]
}
}

Notice how the response mirrors your query structure—this consistency makes GraphQL predictable and easy to work with.

Building Your First Query Flow

Let's create a practical example using a public GraphQL API to fetch country information, which is perfect for learning the basics without needing authentication.

Step-by-Step Flow Creation:

  1. Drag an Inject node onto the canvas to trigger the query.

  2. Drag a GraphQL node onto the canvas and configure it with the endpoint https://countries.trevorblades.com.

  3. Use the following query in the GraphQL node:

    query GetCountries {
    countries {
    code
    name
    capital
    currency
    }
    }
  4. Drag a Debug node onto the canvas and set it to display msg.payload.

  5. Connect the Inject node to the GraphQL node, and then connect the GraphQL node to the Debug node.

  6. Deploy the flow and click the Inject button. Check the Debug panel for the output.

What to Expect in Debug Output

The debug panel will display an array of country objects. Here's a trimmed example of what you should see:

{
"data": {
"countries": [
{
"code": "AD",
"name": "Andorra",
"capital": "Andorra la Vella",
"currency": "EUR"
},
{
"code": "AE",
"name": "United Arab Emirates",
"capital": "Abu Dhabi",
"currency": "AED"
},
{
"code": "US",
"name": "United States",
"capital": "Washington D.C.",
"currency": "USD,USN,USS"
}
// ... more countries
]
}
}

Each country object contains exactly the fields you requested. This demonstrates GraphQL's precision in data fetching.

Working with Dynamic Data

Before writing dynamic queries, note that the GraphQL node has a Syntax setting. You can select GraphQL (default) for standard queries and mutations, or Plain to send raw GraphQL payloads. This is useful for advanced or dynamic queries.

Method 1: Mustache Templates (Simple Approach)

For straightforward use cases, you can inject data directly into your queries using Mustache syntax:

query GetSpecificCountry($countryCode: ID!) {
country(code: $countryCode) {
name
capital
currency
emoji
}
}

Set up your input message in function node:

msg.countryCode = "FR";
return msg;

When to use: Simple queries with one or two variables that don't need type checking.

Method 2: GraphQL Variables

For production applications, GraphQL variables provide better security and maintainability:

Query with Variables:

query GetCountry($code: ID!) {
country(code: $code) {
name
capital
currency
languages {
name
native
}
}
}

Variables Setup:

msg.variables = {
"code": "JP"
};

What to Expect in Debug Output

When querying a single country with variables, you'll see:

{
"data": {
"country": {
"name": "Japan",
"capital": "Tokyo",
"currency": "JPY",
"languages": [
{
"name": "Japanese",
"native": "日本語"
}
]
}
}
}

Modifying Data with Mutations

While queries retrieve data, mutations allow you to create, update, or delete data—similar to POST, PUT, and DELETE operations in REST APIs.

Note: The Countries demo API is read-only and does not include mutations. The examples below use a fictional device schema to illustrate how mutations work in Node-RED.

Basic Mutation Structure

mutation CreateNewDevice($input: DeviceInput!) {
createDevice(input: $input) {
id
name
model
location
createdAt
success
}
}

Setting Up Variables in Node-RED

Use msg.variables to pass dynamic input to your mutation:

msg.variables = {
"input": {
"name": "Raspberry Pi 4A",
"model": "Raspberry Pi 4",
"location": "Factory Floor 1"
}
};
return msg;

What to Expect in Debug Output

When a device is successfully created, you'll see:

{
"data": {
"createDevice": {
"id": "7",
"name": "Raspberry Pi 4A",
"model": "Raspberry Pi 4",
"location": "Factory Floor 1",
"createdAt": "2024-03-21T12:30:45.123Z",
"success": true
}
}
}

If validation fails, the error structure helps you identify the issue:

{
"data": {
"createDevice": {
"id": null,
"success": false,
"errors": [
{
"field": "name",
"message": "Name is required"
}
]
}
}
}

Example: Complete Device Management System

Here's how you might structure a comprehensive device management in GraphQL:

Note: The Device examples are illustrative. The specific types and fields such as DeviceInput, updateDevice, and deactivateDevice must exist in the target GraphQL schema, which can vary depending on the API you are working with.

Fetching Devices with Pagination

query GetDevicesPaginated($limit: Int = 10, $offset: Int = 0, $searchTerm: String) {
devices(limit: $limit, offset: $offset, search: $searchTerm) {
id
name
type
location
lastSeenStatus
createdAt
lastSeenAt
}
deviceCount(search: $searchTerm)
}

This query supports pagination and search, useful when managing large fleets of devices. Note that some GraphQL APIs use cursor-based pagination instead of limit and offset, so you may need to adapt your query accordingly.

Expected Output

{
"data": {
"devices": [
{
"id": "1",
"name": "Raspberry Pi 4A",
"type": "Sensor",
"location": "Factory Floor 1",
"lastSeenStatus": "Online",
"createdAt": "2024-01-15T10:30:00Z",
"lastSeenAt": "2024-03-20T14:22:00Z"
},
{
"id": "2",
"name": "Temperature Monitor A1",
"type": "Sensor",
"location": "Warehouse Section B",
"lastSeenStatus": "Online",
"createdAt": "2024-02-10T08:15:00Z",
"lastSeenAt": "2024-03-21T09:10:00Z"
}
],
"deviceCount": 6
}
}

Creating New Devices

mutation CreateDevice($input: DeviceInput!) {
createDevice(input: $input) {
id
name
type
location
createdAt
success
errors {
field
message
}
}
}

Always return success and any validation errors to confirm the device was created properly.

Expected Output (Success)

{
"data": {
"createDevice": {
"id": "7",
"name": "Smart Thermostat",
"type": "Controller",
"location": "Office Area",
"createdAt": "2024-03-21T12:30:00Z",
"success": true,
"errors": []
}
}
}

Updating Existing Devices

mutation UpdateDevice($id: ID!, $input: DeviceUpdateInput!) {
updateDevice(id: $id, input: $input) {
id
name
type
location
lastSeenStatus
updatedAt
success
}
}

Expected Output

{
"data": {
"updateDevice": {
"id": "1",
"name": "Raspberry Pi 4A",
"type": "Sensor",
"location": "Factory Floor 3",
"lastSeenStatus": "Maintenance",
"updatedAt": "2024-03-21T13:45:00Z",
"success": true
}
}
}

Deleting Devices (Soft Delete)

mutation DeactivateDevice($id: ID!) {
deactivateDevice(id: $id) {
id
isActive
deactivatedAt
success
}
}

Soft deletes allow you to retain historical device data for audits and compliance.

Expected Output

{
"data": {
"deactivateDevice": {
"id": "4",
"isActive": false,
"deactivatedAt": "2024-03-21T14:00:00Z",
"success": true
}
}
}

Advanced Techniques

Custom Headers for Authentication and Metadata

Some GraphQL APIs require additional headers for authentication or client identification. You can add them in the message object before sending the request:

msg.customHeaders = {
"Authorization": "Bearer xyz",
"X-API-Version": "v2",
"X-Client-ID": "node-red-integration"
};
return msg;

Using Fragments for Code Reusability

When queries start to grow, you'll often find yourself requesting the same fields across multiple operations. Fragments let you define those fields once and reuse them, keeping queries clean and consistent.

fragment DeviceBasicInfo on Device {
id
name
type
location
createdAt
}

fragment DeviceOperationalInfo on Device {
...DeviceBasicInfo
lastSeenStatus
lastSeenAt
maintenanceDue
}

query GetDeviceProfile($deviceId: ID!) {
device(id: $deviceId) {
...DeviceOperationalInfo
}
}

Here, DeviceBasicInfo is reused inside DeviceOperationalInfo, so you can easily expand or maintain your schema without duplicating fields.

Expected Output with Fragments

The output includes all fields from both fragments combined:

{
"data": {
"device": {
"id": "1",
"name": "Raspberry Pi 4A",
"type": "Sensor",
"location": "Factory Floor 1",
"createdAt": "2024-01-15T10:30:00Z",
"lastSeenStatus": "Online",
"lastSeenAt": "2024-03-20T14:22:00Z",
"maintenanceDue": "2024-06-15T00:00:00Z"
}
}
}

Notice how the response includes all fields from DeviceBasicInfo (id, name, type, location, createdAt) plus the additional fields from DeviceOperationalInfo (lastSeenStatus, lastSeenAt, maintenanceDue). This demonstrates how fragments compose together to build the complete response.

Complete Example Flow

The following example flow demonstrates creating, reading, updating, and deleting data using GraphQL, including performing queries with fragments for reusable field selections. This flow and the GraphQL node are for demonstration purposes only and do not include a demo API.