---
title: Auto generate REST CRUD with Ent and ogen 
author: MasseElch 
authorURL: "https://github.com/masseelch"
authorImageURL: "https://avatars.githubusercontent.com/u/12862103?v=4"
image: "https://entgo.io/images/assets/ogent/1.png"
---

In the end of 2021 we announced that [Ent](https://entgo.io) got a new official extension to generate a fully
compliant [OpenAPI Specification](https://swagger.io/resources/open-api/)
document: [`entoas`](https://github.com/ent/contrib/tree/master/entoas).

Today, we are very happy to announce that there is a new extension built to work
with `entoas`: [`ogent`](https://github.com/ariga/ogent). It utilizes the power
of [`ogen`](https://github.com/ogen-go/ogen) ([website](https://ogen.dev/docs/intro/)) to provide a type-safe,
reflection-free implementation of the OpenAPI Specification document generated by `entoas`. 

`ogen` is an opinionated Go code generator for OpenAPI Specification v3 documents. `ogen` generates both server and
client implementations for a given OpenAPI Specification document. The only thing left to do for the user is to
implement an interface to access the data layer of any application. `ogen` has many cool features, one of which is
integration with [OpenTelemetry](https://opentelemetry.io/). Make sure to check it out and leave some love. 

The extension presented in this post serves as a bridge between Ent and the code generated
by [`ogen`](https://github.com/ogen-go/ogen). It uses the configuration of `entoas` to generate the missing parts of
the `ogen` code.

The following diagram shows how Ent interacts with both the extensions `entoas` and `ogent` and how `ogen` is involved. 

<div style={{textAlign: 'center'}}>
  <img alt="Diagram" src="https://entgo.io/images/assets/ogent/1.png" />
  <p style={{fontSize: 12}}>Diagram</p>
</div>

If you are new to Ent and want to learn more about it, how to connect to different types of databases, run migrations or
work with entities, then head over to the [Setup Tutorial](https://entgo.io/docs/tutorial-setup/)

The code in this post is available in the modules [examples](https://github.com/ariga/ogent/tree/main/example/todo).

### Getting Started

:::note 
While Ent does support Go versions 1.16+ `ogen` requires you to have at least version 1.17. 
:::

To use the `ogent` extension use the `entc` (ent codegen) package as
described [here](https://entgo.io/docs/code-gen#use-entc-as-a-package). First install both `entoas` and `ogent`
extensions to your Go module:

```shell
go get ariga.io/ogent@main
```

Now follow the next two steps to enable them and to configure Ent to work with the extensions:

1\. Create a new Go file named `ent/entc.go` and paste the following content:

```go title="ent/entc.go"
//go:build ignore

package main

import (
	"log"

	"ariga.io/ogent"
	"entgo.io/contrib/entoas"
	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
	"github.com/ogen-go/ogen"
)

func main() {
	spec := new(ogen.Spec)
	oas, err := entoas.NewExtension(entoas.Spec(spec))
	if err != nil {
		log.Fatalf("creating entoas extension: %v", err)
	}
	ogent, err := ogent.NewExtension(spec)
	if err != nil {
		log.Fatalf("creating ogent extension: %v", err)
	}
	err = entc.Generate("./schema", &gen.Config{}, entc.Extensions(ogent, oas))
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}
```

2\. Edit the `ent/generate.go` file to execute the `ent/entc.go` file:

```go title="ent/generate.go"
package ent

//go:generate go run -mod=mod entc.go
```

With these steps complete, all is set up for generating an OAS document and implementing server code from your schema!

### Generate a CRUD HTTP API Server

The first step on our way to the HTTP API server is to create an Ent schema graph. For the sake of brevity, here is an
example schema to use:

```go title="ent/schema/todo.go"
package schema

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

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

// Fields of the Todo.
func (Todo) Fields() []ent.Field {
	return []ent.Field{
		field.String("title"),
		field.Bool("done"),
	}
}
```

The code above is the "Ent way" to describe a schema-graph. In this particular case we created a todo entity.

Now run the code generator:

```shell
go generate ./...
```

You should see a bunch of files generated by the Ent code generator. The file named `ent/openapi.json` has been
generated by the `entoas` extension. Here is a sneak peek into it:

```json title="ent/openapi.json"
{
  "info": {
    "title": "Ent Schema API",
    "description": "This is an auto generated API description made out of an Ent schema definition",
    "termsOfService": "",
    "contact": {},
    "license": {
      "name": ""
    },
    "version": "0.0.0"
  },
  "paths": {
    "/todos": {
      "get": {
    [...]
```

<div style={{textAlign: 'center'}}>
  <img alt="Swagger Editor Example" src="https://entgo.io/images/assets/ogent/2.png" />
  <p style={{fontSize: 12}}>Swagger Editor Example</p>
</div>

However, this post focuses on the server implementation part therefore we are interested in the directory
named `ent/ogent`. All the files ending in `_gen.go` are generated by `ogen`. The file named `oas_server_gen.go`
contains the interface `ogen`-users need to implement in order to run the server. 

```go title="ent/ogent/oas_server_gen.go"
// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
	// CreateTodo implements createTodo operation.
	//
	// Creates a new Todo and persists it to storage.
	//
	// POST /todos
	CreateTodo(ctx context.Context, req CreateTodoReq) (CreateTodoRes, error)
	// DeleteTodo implements deleteTodo operation.
	//
	// Deletes the Todo with the requested ID.
	//
	// DELETE /todos/{id}
	DeleteTodo(ctx context.Context, params DeleteTodoParams) (DeleteTodoRes, error)
	// ListTodo implements listTodo operation.
	//
	// List Todos.
	//
	// GET /todos
	ListTodo(ctx context.Context, params ListTodoParams) (ListTodoRes, error)
	// ReadTodo implements readTodo operation.
	//
	// Finds the Todo with the requested ID and returns it.
	//
	// GET /todos/{id}
	ReadTodo(ctx context.Context, params ReadTodoParams) (ReadTodoRes, error)
	// UpdateTodo implements updateTodo operation.
	//
	// Updates a Todo and persists changes to storage.
	//
	// PATCH /todos/{id}
	UpdateTodo(ctx context.Context, req UpdateTodoReq, params UpdateTodoParams) (UpdateTodoRes, error)
}
```

`ogent` adds an implementation for
that handler in the file `ogent.go`. To see how you can define what routes to generate and what edges to eager load
please head over to the `entoas` [documentation](https://github.com/ent/contrib/entoas).

The following shows an example for a generated READ route:

```go
// ReadTodo handles GET /todos/{id} requests.
func (h *OgentHandler) ReadTodo(ctx context.Context, params ReadTodoParams) (ReadTodoRes, error) {
	q := h.client.Todo.Query().Where(todo.IDEQ(params.ID))
	e, err := q.Only(ctx)
	if err != nil {
		switch {
		case ent.IsNotFound(err):
			return &R404{
				Code:   http.StatusNotFound,
				Status: http.StatusText(http.StatusNotFound),
				Errors: rawError(err),
			}, nil
		case ent.IsNotSingular(err):
			return &R409{
				Code:   http.StatusConflict,
				Status: http.StatusText(http.StatusConflict),
				Errors: rawError(err),
			}, nil
		default:
			// Let the server handle the error.
			return nil, err
		}
	}
	return NewTodoRead(e), nil
}
```

### Run the server

The next step is to create a `main.go` file and wire up all the ends to create an application-server to serve the
Todo-API. The following main function initializes a SQLite in-memory database, runs the migrations to create all the
tables needed and serves the API as described in the `ent/openapi.json` file on `localhost:8080`: 

```go title="main.go"
package main

import (
	"context"
	"log"
	"net/http"

	"entgo.io/ent/dialect"
	"entgo.io/ent/dialect/sql/schema"
	"<your-project>/ent"
	"<your-project>/ent/ogent"
	_ "github.com/mattn/go-sqlite3"
)

func main() {
	// Create ent client.
	client, err := ent.Open(dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
	if err != nil {
		log.Fatal(err)
	}
	// Run the migrations.
	if err := client.Schema.Create(context.Background(), schema.WithAtlas(true)); err != nil {
		log.Fatal(err)
	}
	// Start listening.
	srv, err := ogent.NewServer(ogent.NewOgentHandler(client))
	if err != nil {
		log.Fatal(err)
	}
	if err := http.ListenAndServe(":8080", srv); err != nil {
		log.Fatal(err)
	}
}
```

After you run the server with `go run -mod=mod main.go` you can work with the API. 

First, let's create a new Todo. For
demonstration purpose we do not send a request body:

```shell
↪ curl -X POST -H "Content-Type: application/json" localhost:8080/todos
{
  "error_message": "body required"
}
```

As you can see `ogen` handles that case for you since `entoas` marked the body as required when attempting to create a
new resource. Let's try again, but this time provide a request body:

```shell
↪ curl -X POST -H "Content-Type: application/json" -d '{"title":"Give ogen and ogent a Star on GitHub"}'  localhost:8080/todos
{
  "error_message": "decode CreateTodo:application/json request: invalid: done (field required)"
}
```

Ooops! What went wrong? `ogen` has your back: the field `done` is required. To fix this head over to your schema
definition and mark the done field as optional:

```go {18} title="ent/schema/todo.go"
package schema

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

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

// Fields of the Todo.
func (Todo) Fields() []ent.Field {
	return []ent.Field{
		field.String("title"),
		field.Bool("done").
		    Optional(),
	}
}
```

Since we made a change to our configuration, we have to re-run code generation and restart the server:

```shell
go generate ./...
go run -mod=mod main.go
```

Now, if we attempt to create the Todo again, see what happens:

```shell
↪ curl -X POST -H "Content-Type: application/json" -d '{"title":"Give ogen and ogent a Star on GitHub"}'  localhost:8080/todos
{
  "id": 1,
  "title": "Give ogen and ogent a Star on GitHub",
  "done": false
}
```

Voila, there is a new Todo item in the database!

Assume you have completed your Todo and starred both [`ogen`](https://github.com/ogen-go/ogen)
and [`ogent`](https://github.com/ariga/ogent) (**you really should!**), mark the todo as done by raising a PATCH
request:

```shell
↪ curl -X PATCH -H "Content-Type: application/json" -d '{"done":true}'  localhost:8080/todos/1
{
  "id": 1,
  "title": "Give ogen and ogent a Star on GitHub",
  "done": true
}
```

### Add custom endpoints

As you can see the Todo is now marked as done. Though it would be cooler to have an extra route for marking a Todo as
done: `PATCH todos/:id/done`. To make this happen we have to do two things: document the new route in our OAS document
and implement the route. We can tackle the first by using the `entoas` mutation builder. Edit your `ent/entc.go` file
and add the route description:

```go {17-37} title="ent/entc.go"
//go:build ignore

package main

import (
	"log"

	"entgo.io/contrib/entoas"
	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
	"github.com/ariga/ogent"
	"github.com/ogen-go/ogen"
)

func main() {
	spec := new(ogen.Spec)
	oas, err := entoas.NewExtension(
		entoas.Spec(spec),
		entoas.Mutations(func(_ *gen.Graph, spec *ogen.Spec) error {
			spec.AddPathItem("/todos/{id}/done", ogen.NewPathItem().
				SetDescription("Mark an item as done").
				SetPatch(ogen.NewOperation().
					SetOperationID("markDone").
					SetSummary("Marks a todo item as done.").
					AddTags("Todo").
					AddResponse("204", ogen.NewResponse().SetDescription("Item marked as done")),
				).
				AddParameters(ogen.NewParameter().
					InPath().
					SetName("id").
					SetRequired(true).
					SetSchema(ogen.Int()),
				),
			)
			return nil
		}),
	)
	if err != nil {
		log.Fatalf("creating entoas extension: %v", err)
	}
	ogent, err := ogent.NewExtension(spec)
	if err != nil {
		log.Fatalf("creating ogent extension: %v", err)
	}
	err = entc.Generate("./schema", &gen.Config{}, entc.Extensions(ogent, oas))
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}
```

After running the code generator (`go generate ./...`) there should be a new entry in the `ent/openapi.json` file:

```json
"/todos/{id}/done": {
  "description": "Mark an item as done",
  "patch": {
    "tags": [
      "Todo"    
    ],
    "summary": "Marks a todo item as done.",
    "operationId": "markDone",
    "responses": {
      "204": {
        "description": "Item marked as done"
      }
    }
  },
  "parameters": [
    {
      "name": "id",
      "in": "path",
      "schema": {
        "type": "integer"
      },
      "required": true
    }
  ]
}
```

<div style={{textAlign: 'center'}}>
  <img alt="Custom Endpoint" src="https://entgo.io/images/assets/ogent/3.png" />
  <p style={{fontSize: 12}}>Custom Endpoint</p>
</div>

The above mentioned `ent/ogent/oas_server_gen.go` file generated by `ogen` will reflect the changes as well: 

```go {21-24} title="ent/ogent/oas_server_gen.go"
// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
	// CreateTodo implements createTodo operation.
	//
	// Creates a new Todo and persists it to storage.
	//
	// POST /todos
	CreateTodo(ctx context.Context, req CreateTodoReq) (CreateTodoRes, error)
	// DeleteTodo implements deleteTodo operation.
	//
	// Deletes the Todo with the requested ID.
	//
	// DELETE /todos/{id}
	DeleteTodo(ctx context.Context, params DeleteTodoParams) (DeleteTodoRes, error)
	// ListTodo implements listTodo operation.
	//
	// List Todos.
	//
	// GET /todos
	ListTodo(ctx context.Context, params ListTodoParams) (ListTodoRes, error)
	// MarkDone implements markDone operation.
	//
	// PATCH /todos/{id}/done
	MarkDone(ctx context.Context, params MarkDoneParams) (MarkDoneNoContent, error)
	// ReadTodo implements readTodo operation.
	//
	// Finds the Todo with the requested ID and returns it.
	//
	// GET /todos/{id}
	ReadTodo(ctx context.Context, params ReadTodoParams) (ReadTodoRes, error)
	// UpdateTodo implements updateTodo operation.
	//
	// Updates a Todo and persists changes to storage.
	//
	// PATCH /todos/{id}
	UpdateTodo(ctx context.Context, req UpdateTodoReq, params UpdateTodoParams) (UpdateTodoRes, error)
}
```

If you'd try to run the server now, the Go compiler will complain about it, because the `ogent` code generator does not
know how to implement the new route. You have to do this by hand. Replace the current `main.go` with the following file
to implement the new method.

```go {15-22,34-38,40} title="main.go"
package main

import (
	"context"
	"log"
	"net/http"

	"entgo.io/ent/dialect"
	"entgo.io/ent/dialect/sql/schema"
	"github.com/ariga/ogent/example/todo/ent"
	"github.com/ariga/ogent/example/todo/ent/ogent"
	_ "github.com/mattn/go-sqlite3"
)

type handler struct {
	*ogent.OgentHandler
	client *ent.Client
}

func (h handler) MarkDone(ctx context.Context, params ogent.MarkDoneParams) (ogent.MarkDoneNoContent, error) {
	return ogent.MarkDoneNoContent{}, h.client.Todo.UpdateOneID(params.ID).SetDone(true).Exec(ctx)
}

func main() {
	// Create ent client.
	client, err := ent.Open(dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
	if err != nil {
		log.Fatal(err)
	}
	// Run the migrations.
	if err := client.Schema.Create(context.Background(), schema.WithAtlas(true)); err != nil {
		log.Fatal(err)
	}
	// Create the handler.
	h := handler{
		OgentHandler: ogent.NewOgentHandler(client),
		client:       client,
	}
	// Start listening.
	srv := ogent.NewServer(h)
	if err := http.ListenAndServe(":8180", srv); err != nil {
		log.Fatal(err)
	}
}

```

If you restart your server you can then raise the following request to mark a todo item as done:

```shell
↪ curl -X PATCH localhost:8180/todos/1/done
```

### Yet to come

There are some improvements planned for `ogent`, most notably a code generated, type-safe way to add filtering
capabilities to the LIST routes. We want to hear your feedback first.

### Wrapping Up

In this post we announced `ogent`, the official implementation generator for `entoas` generated OpenAPI Specification
documents. This extension uses the power of [`ogen`](https://github.com/ogen-go/ogen), a very powerful and feature-rich
Go code generator for OpenAPI v3 documents, to provide a ready-to-use, extensible server RESTful HTTP API servers.

Please note, that both `ogen` and `entoas`/`ogent` have not reached their first major release yet, and it is work in
progress. Nevertheless, the API can be considered stable.

Have questions? Need help with getting started? Feel free to join our [Discord server](https://discord.gg/qZmPgTE6RX) or [Slack channel](https://entgo.io/docs/slack/).

:::note For more Ent news and updates:

- Subscribe to our [Newsletter](https://entgo.substack.com/)
- Follow us on [Twitter](https://twitter.com/entgo_io)
- Join us on #ent on the [Gophers Slack](https://entgo.io/docs/slack)
- Join us on the [Ent Discord Server](https://discord.gg/qZmPgTE6RX)

:::
