Use ent ORM + Atlas for database schemas
Encore has all the tools needed to support ORMs and migration frameworks out-of-the-box through named databases and migration files. Writing plain SQL might not work for your use case, or you may not want to use SQL in the first place.
ORMs like ent and migration frameworks like Atlas can be used with Encore by integrating their logic with a system's database. Encore is not restrictive, it uses plain SQL migration files for its migrations.
- If your ORM of choice can connect to any database using a standard SQL driver, then it can be used with Encore.
- If your migration framework can generate SQL migration files without any modifications, then it can be used with Encore.
Let's take a look at how you can integrate ent with Encore, using Atlas for generating the migration files.
Add ent schemas to a service
Install ent, then initialize your first schema in the service where you want to use it. For example, if you had the following app structure:
/my-app
├── encore.app
└── user // user service
You can then use this command to generate a user schema along with the ent directory that will contain that schema and all future generated files:
$ go run entgo.io/ent/cmd/ent@latest new --target user/ent/schema User
The --target
option sets the schema directory within your Encore system. Each system
should contain its own models and schemas, and its own migration files. Like you would when using
plain SQL.
Add the fields and edges for your new model in the generated file under user/ent/schema/user.go
.
Now, run the following command:
$ go run entgo.io/ent/cmd/ent@latest generate ./user/ent/schema
This generates the ent client files. Run this command again whenever you change the schemas.
Integrating ent with an Encore database
Encore automates database provisioning, and automatically runs migrations in all environments.
To integrate ent with Encore, we need to do three things:
- Create the Encore database
- Set up the ent client to use that database.
- Generate migration files for the ent schema, using Atlas.
Create the Encore database
Create the database using sqldb.NewDatabase
in user/user.go
:
user/user.gopackage user
import "encore.dev/storage/sqldb"
var userDB = sqldb.NewDatabase("user", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
Now, create the migrations
directory, and leave it empty for now:
$ mkdir user/migrations
Connect ent to the database
Next, extend the user service with a Service Struct that creates an ent client connected to the database.
Replace the contents of the user/user.go
file with:
user/user.gopackage user
import (
"encore.dev/storage/sqldb"
"entgo.io/ent/dialect"
entsql "entgo.io/ent/dialect/sql"
"encore.app/user/ent"
)
var userDB = sqldb.NewDatabase("user", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
//encore:service
type Service struct{
ent *ent.Client
}
func initService() (*Service, error) {
driver := entsql.OpenDB(dialect.Postgres, userDB.Stdlib())
entClient := ent.NewClient(ent.Driver(driver))
return &Service{ent: entClient}, nil
}
Now ent is fully wired up to the Encore database, and can be used from the service struct in any API endpoint.
Using Atlas for database migrations
Finally, we'll set up Atlas to generate database migrations for the ent schema.
First, make sure you have Atlas installed.
Then, create the file user/atlas.hcl
containing the following:
user/atlas.hclenv "local" {
src = "ent://ent/schema"
migration {
dir = "file://migrations"
format = golang-migrate
}
format {
migrate {
diff = "{{ sql . \" \" }}"
}
}
}
This tells Atlas to generate migrations for the ent schema, and to output them to the migrations
directory.
Atlas works by comparing the desired ent schema with the current database schema, and generating a migration to bring the database schema in line with the ent schema. This relies on a so-called "shadow database", which is an empty database that Atlas uses to compare the ent schema against.
Fortunately for us, Encore has built-in support for shadow databases.
Create the file user/scripts/generate-migration
containing the following:
user/scripts/generate-migration#!/bin/bash
set -eu
DB_NAME=user
MIGRATION_NAME=${1:-}
# Reset the shadow database
encore db reset --shadow $DB_NAME
# ent executes Go code without initializing Encore when generating migrations,
# so configure the Encore runtime to be aware that this is expected.
export ENCORERUNTIME_NOPANIC=1
# Generate the migration
atlas migrate diff $MIGRATION_NAME --env local --dev-url "$(encore db conn-uri --shadow $DB_NAME)&search_path=public"
Finally, make the script executable, and generate our first migration:
$ chmod +x user/scripts/generate-migration$ cd user && ./scripts/generate-migration init
You should see a new migration file being added to the user/migrations
directory,
containing the schema changes to create the ent models.
You can now run the service with encore run
, and everything should be ready to go!