Contents
layout: doc lang: en translation_key: resp title: Connect over RESP description: Read PostgreSQL rows over RESP2 with redis-cli or Node.js. Includes authentication, client settings, runnable examples and cleanup. section: RESP permalink: /docs/resp.html
last_modified_at: “2026-10-04”
Connect over RESP {#connect-over-resp}
Use MGET to read cached PostgreSQL rows with a RESP2 client.
Start the disposable demo with its RESP configuration:
docker compose -f examples/compose.yaml -f examples/compose.resp.yaml up --build --wait
This enables RESP on 127.0.0.1:56379. Recreating the demo discards its data.
The token below is public and only for this local demo.
TLS and mTLS clients {#tls-mtls-clients}
Prefer TLS for RESP access beyond loopback. Native RESP TLS uses settings
dedicated to the extension and is independent of PostgreSQL ssl_* settings.
These examples use RESP2 and mutual TLS: cache.example must match the server
certificate, ./ca.crt must trust that certificate, and the client certificate
must chain to the CA configured in pg_local_cache.tls_ca_file. For
server-authenticated TLS without mTLS, omit the client certificate and key
options.
redis-cli
export REDISCLI_AUTH="$PGLC_RESP_TOKEN"
redis-cli -2 --tls --cacert ./ca.crt --cert ./client.crt --key ./client.key -h cache.example -p 6380 MGET 'CRUD:app.public.items:{"id":42}'
go-redis
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"log"
"os"
"github.com/redis/go-redis/v9"
)
func main() {
caPEM, err := os.ReadFile("./ca.crt")
if err != nil {
log.Fatal(err)
}
roots := x509.NewCertPool()
if ok := roots.AppendCertsFromPEM(caPEM); !ok {
log.Fatal("no CA certificates found")
}
clientCert, err := tls.LoadX509KeyPair("./client.crt", "./client.key")
if err != nil {
log.Fatal(err)
}
client := redis.NewClient(&redis.Options{
Addr: "cache.example:6380",
Password: os.Getenv("PGLC_RESP_TOKEN"),
Protocol: 2,
DisableIdentity: true,
TLSConfig: &tls.Config{
RootCAs: roots,
MinVersion: tls.VersionTLS12,
ServerName: "cache.example",
Certificates: []tls.Certificate{clientCert},
},
})
defer client.Close()
rows, err := client.MGet(context.Background(), `CRUD:app.public.items:{"id":42}`).Result()
if err != nil {
log.Fatal(err)
}
log.Printf("%v", rows)
}
node-redis
import { readFileSync } from 'node:fs';
import { createClient } from '@redis/client';
const client = createClient({
socket: {
host: 'cache.example',
port: 6380,
tls: true,
servername: 'cache.example',
ca: readFileSync('./ca.crt'),
cert: readFileSync('./client.crt'),
key: readFileSync('./client.key'),
},
password: process.env.PGLC_RESP_TOKEN,
RESP: 2,
disableClientInfo: true,
});
client.on('error', console.error);
await client.connect();
try {
const values = await client.mGet(['CRUD:app.public.items:{"id":42}']);
const rows = values.map(value => value === null ? null : JSON.parse(value));
console.log(rows);
} finally {
await client.close();
}
redis-cli {#redis-cli}
export REDISCLI_AUTH=DemoRespToken_0123456789abcdef0123456789
redis-cli -2 -p 56379 MGET 'CRUD:pglc_demo.public.items:{"id":42}'
The response contains row 42 as JSON. Missing rows return nil.
redis-cli reads the token
from REDISCLI_AUTH.
Node.js {#nodejs}
npm --prefix examples/node-postgres ci --ignore-scripts
npm --prefix examples/node-postgres run resp
The example uses the official @redis/client package and checks order,
duplicates, null input positions and missing rows. Its helper omits null keys
on the wire and restores their positions after decoding.
To connect from your application:
import { createClient } from '@redis/client';
const client = createClient({
url: 'redis://127.0.0.1:56379',
password: process.env.PGLC_RESP_TOKEN,
RESP: 2,
disableClientInfo: true,
});
client.on('error', console.error);
await client.connect();
try {
const values = await client.mGet(['CRUD:pglc_demo.public.items:{"id":42}']);
const rows = values.map(value => value === null ? null : JSON.parse(value));
console.log(rows);
} finally {
await client.close();
}
Set PGLC_RESP_TOKEN to your server’s token. Keep this connection open across
requests. These client settings
select RESP2 and skip Redis-specific client metadata commands.
Use token-only authentication, without a username or Redis database number.
RESP workers use one configured PostgreSQL role for all clients. For reads within a SQL transaction, use Node.js SQL or Go SQL. See the RESP reference for commands and limits.
The listener binds to loopback by default. Prefer TLS beyond loopback; RESP TLS
uses extension-specific settings independent of PostgreSQL ssl_* settings.
With TLS off, a non-loopback plaintext listener requires the explicit
pg_local_cache.allow_plaintext_network=on opt-in, limited to trusted networks.
The demo enables it only inside its container network. See TLS and mTLS
clients. pg_local_cache.enabled is a SIGHUP kill switch.
Each RESP worker applies a reload asynchronously at its next command boundary,
after any command it is executing finishes. local_cache.health() reports
cache_enabled as seen by the SQL session that calls it; it does not
acknowledge that every worker has applied the setting. To disable cache reads
without a restart, run ALTER SYSTEM SET pg_local_cache.enabled = off; and
SELECT pg_reload_conf();. While disabled, each read goes directly to the
source table.
Compare with SQL {#compare-with-sql}
The common benchmark
runs RESP MGET and prepared SQL with the same keys and decoded results in both
Node.js and Go. Published SQL mget measurements from 2.x are historical; that
lane was removed in 3.0.0.
For broader application caching, read the
PostgreSQL and Redis cache-aside guide.
Stop the demo {#stop-the-demo}
docker compose -f examples/compose.yaml -f examples/compose.resp.yaml down