PostgreSQL Chat Message History
After Redis, this lesson stores turns in PostgreSQL instead. RunnableWithMessageHistory and get_session_history stay the same — return PostgresChatMessageHistory from langchain_postgres. Connect with psycopg, run create_tables once, then pass the connection into each history instance.
Why SQL?
Rows stay after a restart and you can inspect them with standard SQL — useful when Redis TTL or in-memory storage is not enough.
Redis or in-memory
History lost on restart (Redis keeps data, but TTL expires)
PostgreSQL
Rows in chat_history table Survives restarts & deploys Query / audit with SQL
RunnableWithMessageHistory wrapper — only get_session_history changes to return PostgresChatMessageHistory.Install packages
Activate the venv from Project Setup, then install the PostgreSQL integration and driver. You also need a PostgreSQL server — see PostgreSQL install docs or run Docker: docker run -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres.
pip install langchain-postgres psycopg[binary]Connect and create tables
Set POSTGRES_URL in your .env file. The demo defaults to postgresql://postgres:postgres@localhost:5432/chatdb. Run create_tables once to ensure the chat_history table exists.
import os
import psycopg
from langchain_postgres import PostgresChatMessageHistory
POSTGRES_URL = os.getenv(
"POSTGRES_URL",
"postgresql://postgres:postgres@localhost:5432/chatdb",
)
TABLE_NAME = "chat_history"
conn = psycopg.connect(POSTGRES_URL)
PostgresChatMessageHistory.create_tables(conn, TABLE_NAME)chat_history table (created by create_tables)
session_id | message (JSON) | created_at
------------|--------------------|-----------
demo-1 | {"type":"human",…} | 2026-06-09
demo-1 | {"type":"ai",…} | 2026-06-09
demo-2 | {"type":"human",…} | 2026-06-09session_id gets its own rows. Query the table directly when you need to verify what was stored.Each invoke
Connect and ensure the table exists once at startup. On each invoke, the wrapper reads rows for session_id, runs the chain, then inserts the new turn.
Steps inside one invoke (session_id=demo-1):
connect
psycopg.connect(POSTGRES_URL)
schema
create_tables(conn, "chat_history")
load
SELECT rows for session_id
chain
prompt | model
save
INSERT human + reply rows
session_id — prior turns load from the table on the next invoke.get_session_history
Return a PostgresChatMessageHistory instance for each session_id. The wrapper calls this on every invoke — same interface as in-memory or Redis backends.
def get_session_history(session_id: str):
return PostgresChatMessageHistory(
TABLE_NAME,
session_id,
sync_connection=conn,
)Wrap and invoke
Same wrapper and invoke call as RunnableWithMessageHistory (In-Memory) — only get_session_history changed.
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)config = {"configurable": {"session_id": "demo-1"}}
reply = chain_with_history.invoke(
{"input": question},
config=config,
)The demo script
postgresql_chat_message_history_demo.py runs the same two HTML <a> questions with session_id="demo-1" stored in PostgreSQL.
Download the code
postgresql_chat_message_history_demo.py
Two HTML questions — PostgreSQL backend
POSTGRES_URL in .env. Venv from Project Setup.Run it
Start PostgreSQL locally (Docker: docker run -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres), create the chatdb database, then:
python postgresql_chat_message_history_demo.pysession_id and rows persist in PostgreSQL.Quick reference
PostgresChatMessageHistory— fromlangchain_postgres; implements the same interface asInMemoryChatMessageHistory.create_tables(conn, table_name)— run once before first use; safe to call on every startup.POSTGRES_URL— connection string; override in.envfor production.sync_connection=conn— pass the openpsycopgconnection to each history instance.- Install:
pip install langchain-postgres psycopg[binary]. - Docs: PostgresChatMessageHistory · RunnableWithMessageHistory · Message history how-to · psycopg3 · PostgreSQL installation.
What's Next
Next: MongoDB Chat Message History — store turns in a document database instead of SQL.