Sequelize_
Use Sequelize with an Appwrite native PostgreSQL database. Configure the postgres dialect, run migrations against the direct connection, and pool runtime traffic from serverless environments.
4 min read
Appwrite's native PostgreSQL database is a standard PostgreSQL engine, so Sequelize works against it with no Appwrite-specific configuration. Point Sequelize's postgres dialect and the pg driver at the connection string from the Connections page and use models, the query interface, and migrations as you would against any PostgreSQL server.
You'll need a native PostgreSQL database in a ready state and its credentials. See native PostgreSQL databases to create one and Connections to retrieve the connection string. The primary user is admin, and the database name is generated per database.
Sequelize 7 (@sequelize/core + @sequelize/postgres) is still alpha. These examples use Sequelize 6 (sequelize + pg), the current stable line. Appwrite connection URLs and ports are unchanged on v7. Stay on v6 unless you are deliberately adopting the alpha: v7 uses a different dialect API, and its CLI is not ready yet.
Set the connection string
In the Console, open your database and click Credentials. Copy the connection string from the DSN or .env tab, or fetch it with postgresql.get(). Put it in your environment, never commit it:
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require"DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"Use the pooled DATABASE_URL (port 6432) for runtime traffic and DIRECT_URL (port 5432) for Sequelize CLI migrations. The TLS parameter (sslmode=require) is part of the connection string Appwrite returns. Appwrite Cloud terminates TLS at the edge and forwards traffic to your database over the internal network. For full certificate verification (verify-full) or mTLS, see Network security.
Install and configure Sequelize
Install Sequelize and the PostgreSQL driver:
npm install sequelize pg pg-hstorenpm install -D sequelize-cli typescript @types/nodeCreate a Sequelize instance for runtime traffic. Prefer the URL form and set SSL with verification when you need to control TLS explicitly:
import { Sequelize, DataTypes } from 'sequelize';
export const sequelize = new Sequelize(process.env.DATABASE_URL!, { dialect: 'postgres', dialectOptions: { ssl: { require: true, rejectUnauthorized: true } }, logging: false, pool: { max: 10, min: 0, idle: 10000 }});
export const User = sequelize.define( 'User', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, email: { type: DataTypes.TEXT, allowNull: false, unique: true }, createdAt: { type: DataTypes.DATE, allowNull: false, field: 'created_at' } }, { tableName: 'users', updatedAt: false });ssl: { require: true, rejectUnauthorized: true } validates the server certificate against Node's built-in CA store. You can also pass discrete host, port, database, username, and password options instead of a URL; keep dialect: 'postgres' and the same dialectOptions.ssl settings.
Run migrations
Use Sequelize CLI migrations against the direct engine port (5432). DDL and migration metadata updates need a session-level connection, so do not point the CLI at the transaction-mode pooler.
sequelize-cli init generates localhost defaults. Replace them with a concrete .sequelizerc and config that read DIRECT_URL.
Create .sequelizerc at the project root:
const path = require('path');
module.exports = { config: path.resolve('config', 'config.js'), 'models-path': path.resolve('models'), 'seeders-path': path.resolve('seeders'), 'migrations-path': path.resolve('migrations')};Create config/config.js so every CLI environment uses DIRECT_URL:
require('dotenv').config();
const shared = { url: process.env.DIRECT_URL, dialect: 'postgres', dialectOptions: { ssl: { require: true, rejectUnauthorized: true } }, logging: false};
module.exports = { development: shared, test: shared, production: shared};Then generate and apply migrations:
npx sequelize-cli migration:generate --name create-usersnpx sequelize-cli db:migrateExample migration:
'use strict';
module.exports = { async up(queryInterface, Sequelize) { await queryInterface.createTable('users', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, email: { type: Sequelize.TEXT, allowNull: false, unique: true }, created_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.fn('now') } }); },
async down(queryInterface) { await queryInterface.dropTable('users'); }};Prefer migrations over sync({ alter: true }) in shared and production environments. The primary admin user owns the generated database and can run schema changes. Use narrower database roles for application traffic that does not need DDL privileges.
Query with Sequelize
Once the schema is migrated, authenticate and use models:
import { sequelize, User } from './db';
await sequelize.authenticate();
const user = await User.create({ email: 'ada@example.com' });
const recent = await User.findAll({ order: [['createdAt', 'DESC']], limit: 10});On long-running servers, create the Sequelize instance once at module scope and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse the pool, and rely on the Appwrite pooler for runtime traffic. Call sequelize.close() on shutdown.
Pool connections from serverless
Each running instance opens its own connections to the engine. On serverless and edge platforms (Vercel, Netlify, Cloudflare), short-lived instances can fan out into more backend connections than the engine allows. Route runtime traffic through the connection pooler. The primary DATABASE_URL already uses the pooler port (6432) on the same hostname.
The pooler defaults to transaction mode (PgBouncer-style multiplexing). That mode does not keep a backend connection across statements, so session-scoped prepared statements, advisory locks, LISTEN/NOTIFY, temporary tables, and SET LOCAL are unsafe. Keep Sequelize CLI migrations pointed at DIRECT_URL.
Keep the Sequelize pool.max small on serverless. If your application depends on session-level features, switch the pooler to session mode or use the direct port. See the pooler page for the trade-offs.
Use a branch for previews and CI
Branches are isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:
- Create a branch from the API and read its
connectionString. - Export it as
DIRECT_URL(and the pooled variant asDATABASE_URL). - Run
sequelize-cli db:migrateand your test suite against the branch. - Delete the branch when the job finishes.
Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.
Related
Connections
Retrieve credentials, rotate the password, and create database roles.
Connection pooler
Pool modes, ports, and read/write splitting for serverless workloads.
Branches
Ephemeral database copies for preview environments and CI.
Network security
TLS modes, certificate verification, mTLS, and IP allowlists.
Was this page helpful?
Share what worked or what we should fix. Once approved, our agents automatically apply suggested updates to the docs.