Laravel
Laravel connects to DB9 through PHP’s PDO PostgreSQL driver and Eloquent ORM. No special adapter or driver is needed — DB9 speaks the PostgreSQL wire protocol, so Laravel’s pgsql connection works out of the box.
Prerequisites
Section titled “Prerequisites”- A DB9 database (create one)
- PHP 8.1+
- Composer
- Laravel 10+
Create a DB9 Database
Section titled “Create a DB9 Database”db9 create --name laravel-appdb9 create prints the connection string, with the admin password, once — copy it from the Connection String: line. It has this shape:
postgresql://YOUR_TENANT_ID.admin:YOUR_PASSWORD@pg.d0000.db9.io:5432/postgresYOUR_TENANT_ID is the tenant ID prefix in the generated connection string. Copy the username from the Connection String: line printed by db9 create or from db9 db connect. The examples below use pg.db9.io:5433, which reaches the same server; either host and port works. db9 db status laravel-app shows the connection string without the password; if you lose it, db9 db reset-password laravel-app sets a new password and prints a new connection string.
Settings that take the password on its own need it unencoded: the connection string URL-encodes it (%2B is +, %3D is =), while db9 create --show-password and db9 db reset-password also print it as plain text on the Admin Pass: line.
Set the connection details as environment variables:
export DB9_HOST="pg.db9.io"export DB9_PASSWORD="YOUR_PASSWORD" # unencodedConfigure Database Connection
Section titled “Configure Database Connection”Update your .env file with the DB9 connection details:
DB_CONNECTION=pgsqlDB_HOST=pg.db9.ioDB_PORT=5433DB_DATABASE=postgresDB_USERNAME=YOUR_TENANT_ID.adminDB_PASSWORD=YOUR_PASSWORDThen update the pgsql section of config/database.php to include sslmode:
'pgsql' => [ 'driver' => 'pgsql', 'host' => env('DB_HOST', 'pg.db9.io'), 'port' => env('DB_PORT', '5433'), 'database' => env('DB_DATABASE', 'postgres'), 'username' => env('DB_USERNAME', 'YOUR_TENANT_ID.admin'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, 'search_path' => 'public', 'sslmode' => 'require',],Define Models
Section titled “Define Models”If the application already has app/Models/User.php and a create_users_table migration,
edit those files. Generating another users migration makes migrate fail with
relation "users" already exists.
Otherwise, generate the model and migration:
php artisan make:model User -mGenerate a Post model with a migration:
php artisan make:model Post -mEdit the migration for users:
public function up(): void{ Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamps(); });}Edit the migration for posts:
public function up(): void{ Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body')->nullable(); $table->boolean('published')->default(false); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->timestamps(); });}Add relationships to the models:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Model{ protected $fillable = ['name', 'email'];
public function posts(): HasMany { return $this->hasMany(Post::class); }}namespace App\Models;
use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Post extends Model{ protected $fillable = ['title', 'body', 'published', 'user_id'];
public function user(): BelongsTo { return $this->belongsTo(User::class); }}Run Migrations
Section titled “Run Migrations”php artisan migrateVerify the tables exist:
php artisan tinker>>> \DB::select('SELECT tablename FROM pg_tables WHERE schemaname = \'public\'');Controllers and Routes
Section titled “Controllers and Routes”Generate a resource controller:
php artisan make:controller UserController --resourceAdd CRUD logic:
namespace App\Http\Controllers;
use App\Models\User;use Illuminate\Http\Request;
class UserController extends Controller{ public function index() { return User::all(); }
public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', ]);
$user = User::create($validated);
return response()->json($user, 201); }
public function show(User $user) { return $user->load('posts'); }
public function update(Request $request, User $user) { $validated = $request->validate([ 'name' => 'sometimes|string|max:255', 'email' => 'sometimes|email|unique:users,email,' . $user->id, ]);
$user->update($validated);
return response()->json($user); }
public function destroy(User $user) { $user->delete();
return response()->noContent(); }}Register the routes:
use App\Http\Controllers\UserController;
Route::apiResource('users', UserController::class);For applications configured in bootstrap/app.php, register routes/api.php in the existing
withRouting call. Creating the route file alone leaves /api/users unregistered:
->withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up',)Start the server and test:
php artisan servecurl http://localhost:8000/api/usersProduction Notes
Section titled “Production Notes”- Host and port:
pg.db9.io:5433and thepg.d0000.db9.io:5432thatdb9 createprints reach the same server; port5432works on either host. - TLS: Always use
sslmode=requirein your database config for DB9’s hosted service. - Connection pooling: Set
DB_CONNECTIONpool size through Laravel’sdatabase.phpconfig or use an external pooler like PgBouncer for high-traffic apps. - Database name: The database is always
postgres. Do not attempt to create additional databases. - Queue workers: If running Laravel queues with a database driver, each worker opens its own connection. Size your pool accordingly.
Troubleshooting
Section titled “Troubleshooting”Connection refused on port 5432
Section titled “Connection refused on port 5432”DB9 accepts port 5432 as well as 5433, so the client is not reaching DB9 at all. A refusal from localhost or 127.0.0.1 means Laravel is not using the DB9 host. Check your .env file:
DB_HOST=pg.db9.ioDB_PORT=5433PDO pgsql extension not found
Section titled “PDO pgsql extension not found”Install the PHP PostgreSQL extension. On Ubuntu/Debian:
sudo apt install php-pgsqlOn macOS with Homebrew:
brew install phpThe pdo_pgsql extension is included by default. Verify with php -m | grep pgsql.
Migration errors: relation already exists
Section titled “Migration errors: relation already exists”If tables were created manually before running migrations, use the --pretend flag to check what SQL would run, then mark migrations as complete:
php artisan migrate --pretendphp artisan migrate --path=database/migrations/xxxx_xx_xx_specific_migration.phpSSL connection errors
Section titled “SSL connection errors”Confirm sslmode is set to require in your config/database.php under the pgsql connection. If you are using a DATABASE_URL environment variable, append ?sslmode=require:
DATABASE_URL="postgresql://YOUR_TENANT_ID.admin:YOUR_PASSWORD@pg.db9.io:5433/postgres?sslmode=require"Authentication failed
Section titled “Authentication failed”Verify the username format. DB9 expects <tenant_id>.admin. Use the complete username from the generated connection string or db9 db connect; laravel-app.admin fails even with the right password.
Next Pages
Section titled “Next Pages”- Connect — connection strings and authentication
- Production Checklist — deployment readiness