Progress OpenEdge

Fixing a Progress ODBC Hang in a Node.js API

A Node.js API can look healthy in PM2 but still hang during a database request. In this guide, we explain how stale Progress ODBC connections caused a silent request hang and how fresh connections fixed it.

The Problem

We recently debugged a Node.js API connected to a Progress OpenEdge database through the Node odbc package. After a restart, everything worked properly. But after the API ran for some time, a test login request started hanging.

POST /api/test/login

The browser kept loading, but the API did not return a response. PM2 still showed the service as online. CPU and memory looked normal. There was no clear database error in the logs.

What We Saw in the Logs

The request was reaching the API:

[10:15:22 UTC] INFO: incoming request
method: "POST"
url: "/api/test/login"

But there was no matching response log. After restarting the API, the same test request completed successfully:

pm2 restart test-api --update-env

[10:18:40 UTC] INFO: request completed
statusCode: 200
responseTime: 2150 ms

This told us the Node.js process was alive, but one part of the request was waiting forever.

Finding the Real Cause

The test login flow was calling Progress through ODBC:

const user = await findTestUser(username);
const permissions = await findTestUserPermissions(user.type);

Once timeout handling was added around the ODBC calls, the silent hang became visible:

{
  "success": false,
  "message": "Timed out running ODBC query against Progress database: test_database",
  "error": {
    "code": "ODBC_QUERY_TIMEOUT"
  }
}

So the request was not stuck in the route logic. It was stuck while running an ODBC query.

Why the SQL Was Not the Problem

We tested the same SQL directly through a Progress ODBC test tool. It returned quickly. That was an important clue.

If the SQL works outside Node.js but hangs after the API has been running for some time, the issue is usually not the query itself. It is more likely related to the ODBC connection lifecycle.

Restart API -> test login works
Wait some time -> test login hangs
Restart API -> test login works again

This pattern often points to stale pooled connections.

Root Cause

The API was using an ODBC connection pool:

const pool = await odbc.pool(...);
const connection = await pool.connect();

After each query, the code closed the connection:

await connection.close();

With pooled connections, this does not always close the real database connection. In many cases, it only returns the connection back to the pool.

That means the API may reuse an old Progress ODBC connection later. If that connection becomes stale after idle time, network changes, or database-side timeout behavior, the next query can hang.

The Fix

We changed the database wrapper to open a fresh Progress ODBC connection for each query and close it after the query finishes.

const connection = await odbc.connect({
  connectionString: config.connectionString,
  connectionTimeout: connectionTimeoutSeconds,
  loginTimeout: loginTimeoutSeconds,
});

The query lifecycle became simple:

Open a fresh ODBC connection
Run the query
Close the connection

This matched the manual test behavior, where the query was always fast because every test used a fresh connection.

Timeout Protection

We also added application-level timeouts around the risky parts:

  • Opening the ODBC connection
  • Running the ODBC query
  • Closing the ODBC connection

Now the API returns a controlled error instead of loading forever:

{
  "success": false,
  "message": "Timed out running ODBC query against Progress database",
  "error": {
    "code": "ODBC_QUERY_TIMEOUT"
  }
}

Final Result

The issue was not caused by PM2, Nginx, the API framework, or the SQL query. The actual problem was stale reused Progress ODBC connections inside the Node.js ODBC pool.

After switching to fresh connections and adding timeout handling, the API became stable and much easier to debug.