How to Use ent for Database Schema and Code Generation

Use `ent` by defining your data model in Go structs and then running the CLI to generate a type-safe, performant codebase that handles schema migrations and query building automatically.

Use ent by defining your data model in Go structs and then running the CLI to generate a type-safe, performant codebase that handles schema migrations and query building automatically. You define the schema in a schema/ directory, run ent generate to create the Go code, and use the generated client to interact with your database without writing raw SQL.

First, initialize the project and define your schema. Create a schema/user.go file where you describe the entity and its fields using the ent schema package:

// schema/user.go
package schema

import (
	"entgo.io/ent"
	"entgo.io/ent/schema/field"
)

// User holds the schema definition for the User entity.
type User struct {
	ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
	return []ent.Field{
		field.String("name").
			NotEmpty(),
		field.String("email").
			Unique().
			NotEmpty(),
		field.Int("age").
			Default(0),
	}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
	return nil
}

Next, generate the code and run the migration. Execute the following commands in your terminal to scaffold the client and apply the schema to your database:

# Generate the code from the schema definitions
ent generate ./schema

# Run the migration to create tables in the database (requires a running DB)
entc generate ./schema
go run ./cmd/migrate

Once generated, you can use the client in your application logic. The generated code provides a fluent API for queries, ensuring type safety and preventing SQL injection:

// main.go
package main

import (
	"context"
	"log"
	"your-project/ent"
	"your-project/ent/user"
)

func main() {
	// Open a connection to the database
	client, err := ent.Open("sqlite3", "file:ent?mode=memory&_fk=1")
	if err != nil {
		log.Fatalf("failed opening connection to sqlite: %v", err)
	}
	defer client.Close()

	ctx := context.Background()

	// Create a new user
	u, err := client.User.Create().
		SetName("Alice").
		SetEmail("alice@example.com").
		SetAge(30).
		Save(ctx)
	if err != nil {
		log.Fatalf("failed creating user: %v", err)
	}

	// Query users with specific filters
	users, err := client.User.Query().
		Where(user.Email("alice@example.com")).
		All(ctx)
	if err != nil {
		log.Fatalf("failed querying users: %v", err)
	}

	log.Printf("Found %d users", len(users))
}

This approach keeps your database schema and application logic tightly coupled. When you modify the schema file, you simply run ent generate again to update the code, and entc migrate to update the database structure. This eliminates the need for manual SQL schema management and reduces boilerplate code significantly.