Database Setup
Medusa uses PostgreSQL as its primary database. This guide covers database setup, configuration, and migrations.
PostgreSQL Requirements
- PostgreSQL version: 12 or higher (PostgreSQL 14+ recommended)
- Database user: Must have create privileges
- Extensions: No special extensions required by default
Database Connection
Connection URL
Medusa connects to PostgreSQL using a connection URL:
DATABASE_URL=postgres://[user][:password]@[host][:port]/[dbname]
Where:
[user]: Your PostgreSQL username (required)
[:password]: User password (optional, prefix with :)
[host]: Database host (required, e.g., localhost)
[:port]: PostgreSQL port (optional, default: 5432, prefix with :)
[dbname]: Database name (required)
Example Connection URLs
# Local development
DATABASE_URL=postgres://postgres@localhost/medusa-store
# With password
DATABASE_URL=postgres://postgres:password123@localhost/medusa-store
# Custom port
DATABASE_URL=postgres://postgres:password123@localhost:5433/medusa-store
# Remote database
DATABASE_URL=postgres://user:[email protected]:5432/medusa-production
# With SSL (add ?ssl=true or ?sslmode=require)
DATABASE_URL=postgres://user:[email protected]:5432/medusa?sslmode=require
Creating a Database
Using PostgreSQL CLI
# Connect to PostgreSQL
psql -U postgres
# Create database
CREATE DATABASE medusa_store;
# Create user (optional)
CREATE USER medusa_user WITH PASSWORD 'secure_password';
# Grant privileges
GRANT ALL PRIVILEGES ON DATABASE medusa_store TO medusa_user;
Using createdb Command
createdb -U postgres medusa_store
Medusa can automatically create the database on first run if it doesn’t exist and the user has create privileges.
Database Configuration
Basic Configuration
import { defineConfig } from "@medusajs/utils"
export default defineConfig({
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
},
})
Advanced Configuration
import { defineConfig } from "@medusajs/utils"
export default defineConfig({
projectConfig: {
// Connection URL
databaseUrl: process.env.DATABASE_URL,
// Schema (default: "public")
databaseSchema: "public",
// Connection pool options
databaseDriverOptions: {
pool: {
// Minimum connections in pool
min: 2,
// Maximum connections in pool
max: 10,
// Idle timeout in milliseconds
idleTimeoutMillis: 30000,
// Reap interval in milliseconds
reapIntervalMillis: 1000,
// Create retry interval in milliseconds
createRetryIntervalMillis: 200,
},
},
},
})
Connection Pool Sizing
Recommended pool sizes based on your deployment:
- Development:
min: 2, max: 5
- Production (single instance):
min: 2, max: 10
- Production (multiple instances):
min: 2, max: 5 per instance
Be careful with connection pool sizes. PostgreSQL has a maximum connection limit (default: 100). If you have multiple application instances, ensure the total connections don’t exceed the limit.
SSL Configuration
For production databases, enable SSL:
# Basic SSL
DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require
# Verify CA certificate
DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=verify-ca
# Verify full SSL
DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=verify-full
SSL modes:
disable: No SSL
require: SSL required, but don’t verify certificate
verify-ca: Verify server certificate against CA
verify-full: Verify certificate and hostname
Database Migrations
Migrations ensure your database schema is up-to-date with your Medusa version and modules.
Running Migrations
Before starting your application, run migrations:
This command:
- Creates the database if it doesn’t exist
- Creates the migrations table
- Runs all pending module migrations
- Synchronizes link definitions between modules
- Executes migration scripts
Migration Options
# Run migrations (default)
npx medusa db:migrate
# Skip link synchronization
npx medusa db:migrate --skip-links
# Skip migration scripts
npx medusa db:migrate --skip-scripts
# Execute all link migrations (not just safe ones)
npx medusa db:migrate --execute-all-links
# Set migration concurrency
npx medusa db:migrate --concurrency 5
Always run migrations before starting your application in production. Running without migrations will cause errors.
Migration Process
Medusa uses MikroORM for migrations. The migration process:
- Module Migrations: Each module maintains its own migrations
- Link Synchronization: Creates join tables for module relationships
- Migration Scripts: Custom data transformations and updates
Checking Migration Status
Migrations are tracked in the mikro_orm_migrations table:
-- Check migration status
SELECT * FROM mikro_orm_migrations ORDER BY executed_at DESC;
Database Schemas
By default, Medusa uses the public schema. You can configure a custom schema:
export default defineConfig({
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
databaseSchema: "medusa", // Custom schema
},
})
Create the schema before running migrations:
Database Backup and Restore
Backup Database
# Backup to file
pg_dump -U postgres -d medusa_store -F c -f medusa_backup.dump
# Backup with compression
pg_dump -U postgres -d medusa_store -F c -Z 9 -f medusa_backup.dump
# Backup specific schema
pg_dump -U postgres -d medusa_store -n public -F c -f medusa_backup.dump
Restore Database
# Restore from backup
pg_restore -U postgres -d medusa_store -c medusa_backup.dump
# Create database and restore
createdb -U postgres medusa_store
pg_restore -U postgres -d medusa_store medusa_backup.dump
Always test your backup and restore process before you need it in production.
Indexes
Medusa automatically creates necessary indexes through migrations. For custom optimizations:
-- Check missing indexes
SELECT schemaname, tablename, attname, n_distinct, correlation
FROM pg_stats
WHERE schemaname = 'public'
ORDER BY n_distinct DESC;
-- Create custom index
CREATE INDEX CONCURRENTLY idx_custom ON your_table(column_name);
Connection Pooling
Use connection pooling for better performance:
export default defineConfig({
projectConfig: {
databaseDriverOptions: {
pool: {
min: 2,
max: 10,
idleTimeoutMillis: 30000,
},
},
},
})
Monitor slow queries:
-- Enable query logging
ALTER DATABASE medusa_store SET log_min_duration_statement = 1000;
-- View slow queries
SELECT * FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
Troubleshooting
Connection Refused
Error: connect ECONNREFUSED 127.0.0.1:5432
Solutions:
- Check if PostgreSQL is running:
sudo systemctl status postgresql
- Verify the host and port in
DATABASE_URL
- Check firewall rules
Authentication Failed
Error: password authentication failed for user "postgres"
Solutions:
- Verify username and password in
DATABASE_URL
- Check
pg_hba.conf for authentication settings
- Reset user password if needed
Database Does Not Exist
Error: database "medusa_store" does not exist
Solutions:
- Create the database manually
- Ensure the user has create privileges for auto-creation
- Check the database name in
DATABASE_URL
Too Many Connections
Error: sorry, too many clients already
Solutions:
- Reduce connection pool size in
databaseDriverOptions
- Increase PostgreSQL
max_connections setting
- Use a connection pooler like PgBouncer
Migration Failures
Solutions:
- Check PostgreSQL logs for details
- Ensure user has necessary privileges
- Verify no concurrent migration processes
- Check for conflicting data or constraints
Production Best Practices
Security
- Use strong passwords: Generate random, complex passwords
- Limit privileges: Grant only necessary permissions
- Enable SSL: Use SSL for all connections
- Network isolation: Use private networks or VPNs
- Regular backups: Automate daily backups
Monitoring
- Connection count: Monitor active connections
- Query performance: Track slow queries
- Disk usage: Monitor database size
- Replication lag: If using replication
Maintenance
-- Vacuum and analyze
VACUUM ANALYZE;
-- Reindex database
REINDEX DATABASE medusa_store;
-- Check database size
SELECT pg_size_pretty(pg_database_size('medusa_store'));
Schedule regular VACUUM and ANALYZE operations during low-traffic periods to maintain optimal performance.
Multiple Database Support
Medusa supports module-specific databases:
import { Modules } from "@medusajs/utils"
export default defineConfig({
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
},
modules: [
{
key: Modules.PRODUCT,
resolve: "@medusajs/product",
options: {
// Custom database for product module
databaseUrl: process.env.PRODUCT_DATABASE_URL,
},
},
],
})
Environment variables:
# Shared database
DATABASE_URL=postgres://localhost/medusa_shared
# Module-specific database
PRODUCT_DATABASE_URL=postgres://localhost/medusa_products