How to Use Iris and PostgreSQL for Web Development

Gerasimos (Makis) Maropoulos - Jul 2 '23 - - Dev Community

Iris is a fast and lightweight web framework for Go that offers a rich set of features and a high-performance engine. PostgreSQL is a powerful and reliable relational database system that supports advanced data types and functions. Together, they can form a solid foundation for building modern web applications.

But how can you connect Iris and PostgreSQL in a simple and type-safe way? How can you perform common database operations without writing too much boilerplate code? How can you handle transactions, schema creation, query tracing and error handling in a consistent manner?

The answer is PG middleware, a package for Iris that provides easy and type-safe access to PostgreSQL database. In this article, we will show you how to use PG middleware to create a simple REST API for managing customers.

What is PG middleware?

PG middleware is a package for Iris web framework that provides easy and type-safe access to PostgreSQL database. It has the following features:

  • It supports PostgreSQL 9.5 and above.
  • It uses pg package and pgx driver under the hood.
  • It supports transactions, schema creation and validation, query tracing and error handling.
  • It allows registering custom types and table models using a schema object.
  • It provides a generic repository interface for common CRUD operations.

How to install PG middleware?

To install PG middleware, you need to use the following command:

go get github.com/iris-contrib/middleware/pg@master
Enter fullscreen mode Exit fullscreen mode

How to use PG middleware?

To use PG middleware, you need to follow these steps:

  1. Import the package in your code:
import (
    "github.com/kataras/iris/v12"

    "github.com/iris-contrib/middleware/pg"
)
Enter fullscreen mode Exit fullscreen mode
  1. Define your database table models as structs with pg tags:
// The Customer database table model.
type Customer struct {
    ID   string `json:"id" pg:"type=uuid,primary"`
    Name string `json:"name" pg:"type=varchar(255)"`
}
Enter fullscreen mode Exit fullscreen mode

The json tag defines how the struct fields are encoded or decoded as JSON. The pg tag defines how the struct fields are mapped to the database table columns. You can specify the column type, constraints, indexes and other options using the pg tag. Read more at pg repository.

  1. Create a schema object and register your models:
schema := pg.NewSchema()
schema.MustRegister("customers", Customer{})
Enter fullscreen mode Exit fullscreen mode

The schema object is used to store the metadata of your database tables and models. You need to register your models with the schema object using the MustRegister method. The first argument is the table name, and the second argument is the model type.

  1. Create a PG middleware instance with the schema and database options:
opts := pg.Options{
    Host:          "localhost",
    Port:          5432,
    User:          "postgres",
    Password:      "admin!123",
    DBName:        "test_db",
    Schema:        "public",
    SSLMode:       "disable",
    Transactional: true,
    Trace:         true,
    CreateSchema:  true,
    CheckSchema:   true,
    ErrorHandler: func(ctx iris.Context, err error) {
        ctx.StopWithError(iris.StatusInternalServerError, err)
    },
}

p := pg.New(schema, opts)
Enter fullscreen mode Exit fullscreen mode

The options struct defines the configuration parameters for connecting to the database and using the middleware features. You need to specify the host, port, user, password, dbname, schema and sslmode fields for establishing the connection. You can also enable or disable the transactional feature, which wraps each request handler in a database transaction. You can also enable or disable the trace feature, which logs each query executed by the middleware. You can also enable or disable the createSchema feature, which creates the schema if it doesn't exist in the database. You can also enable or disable the checkSchema feature, which checks the schema for missing tables and columns and reports any discrepancies. You can also provide an errorHandler function, which handles any errors occurred during the middleware execution.

The New function creates a new PG middleware instance with the given schema and options.

  1. Attach the middleware handler to your Iris app or routes:
app := iris.New()

postgresMiddleware := newPostgresMiddleware()

{
    customerAPI := app.Party("/api/customer", postgresMiddleware)
    customerAPI.Post("/", createCustomer)
    customerAPI.Get("/{id:uuid}", getCustomer)
    customerAPI.Put("/{id:uuid}", updateCustomer)
    customerAPI.Delete("/{id:uuid}", deleteCustomer)
}
Enter fullscreen mode Exit fullscreen mode

The middleware handler is a function that takes an Iris context and calls the next handler in the chain. You can attach the middleware handler to your Iris app or routes using the Use or Party methods. In this example, we create a subrouter for the customer API and apply the middleware handler to it.

  1. Use the pg.DB or pg.Repository package-level functions to access the database instance or the repository interface in your handlers:
func createCustomer(ctx iris.Context) {
    var payload = struct {
        Name string `json:"name"`
    }{}
    err := ctx.ReadJSON(&payload)
    if err != nil {
        ctx.StopWithError(iris.StatusBadRequest, err)
        return
    }

    // Get the current database instance through pg.DB middleware package-level function.
    // db := pg.DB(ctx)
    // [Work with db instance...]
    // OR, initialize a new repository of Customer type and work with it (type-safety).
    customers := pg.Repository[Customer](ctx)

    // Insert a new Customer.
    customer := Customer{
        Name: payload.Name,
    }
    err = customers.InsertSingle(ctx, customer, &customer.ID)
    if err != nil {
        ctx.StopWithError(iris.StatusInternalServerError, err)
        return
    }

    // Display the result ID.
    ctx.StatusCode(iris.StatusCreated)
    ctx.JSON(iris.Map{"id": customer.ID})
}

func getCustomer(ctx iris.Context) {
    // Get the id from the path parameter.
    id := ctx.Params().Get("id")

    // Get the repository of Customer type through pg.Repository middleware package-level function.
    customers := pg.Repository[Customer](ctx)

    // Get the customer by the id.
    customer, err := customers.SelectByID(ctx, id)
    if err != nil {
        if pg.IsErrNoRows(err) {
            ctx.StopWithStatus(iris.StatusNotFound)
        } else {
            ctx.StopWithError(iris.StatusInternalServerError, err)
        }

        return
    }

    // Display the retrieved Customer.
    ctx.JSON(customer)
}

func updateCustomer(ctx iris.Context) {
    // Get the id from the path parameter.
    id := ctx.Params().Get("id")

    var payload = struct {
        Name string `json:"name"`
    }{}
    err := ctx.ReadJSON(&payload)
    if err != nil {
        ctx.StopWithError(iris.StatusBadRequest, err)
        return
    }

    // Get the repository of Customer type through pg.Repository middleware package-level function.
    customers := pg.Repository[Customer](ctx)

    // Update the customer by the id and name.
    customer := Customer{
        ID: id,
        Name: payload.Name,
    }
    _, err = customers.UpdateOnlyColumns(ctx, []string{"name"}, customer)
    // OR customers.Update(ctx, customer)
    if err != nil {
        if pg.IsErrNoRows(err) {
            ctx.StopWithStatus(iris.StatusNotFound)
        } else {
            ctx.StopWithError(iris.StatusInternalServerError, err)
        }

        return
    }

    // Display a success message.
    ctx.StatusCode(iris.StatusOK)
    ctx.JSON(iris.Map{"message": "Customer updated successfully"})
}

func deleteCustomer(ctx iris.Context) {
    // Get the id from the path parameter.
    id := ctx.Params().Get("id")

    // Get the repository of Customer type through pg.Repository middleware package-level function.
    customers := pg.Repository[Customer](ctx)

    // Delete the customer by the id.
    _, err := customers.Delete(ctx, Customer{ID: id})
    if err != nil {
        if pg.IsErrNoRows(err) {
            ctx.StopWithStatus(iris.StatusNotFound)
        } else {
            ctx.StopWithError(iris.StatusInternalServerError, err)
        }

        return
    }

    // Display a success message.
    ctx.StatusCode(iris.StatusOK)
    ctx.JSON(iris.Map{"message": "Customer deleted successfully"})
}
Enter fullscreen mode Exit fullscreen mode

The pg.DB function returns the current database instance associated with the Iris context. You can use this instance to perform any database operations using the pg package API. The pg.Repository function returns a generic repository interface for the given model type associated with the Iris context. You can use this interface to perform common CRUD operations using type-safe methods. In this example, we use the repository interface to insert and select customers.

How to run the example?

To run the example, you need to:

  1. Clone the PG middleware repository and navigate to the _examples/basic folder.
  2. Install the dependencies using go mod tidy.
  3. Start a PostgreSQL server and create a database named test_db.
  4. Run the main.go file using go run main.go.
  5. Use a tool like Postman or curl to test the API endpoints:
# Create a new customer
curl -X POST -H "Content-Type: application/json" -d '{"name":"Alice"}' http://localhost:8080/api/customer

# Get a customer by id
curl -X GET http://localhost:8080/api/customer/1f8c9a7c-6b7c-4f0e-8a1d-9f2a9c3b7b8e

# Update a customer by id
curl -X PUT -H "Content-Type: application/json" -d '{"name":"Bob"}' http://localhost:8080/api/customer/1f8c9a7c-6b7c-4f0e-8a1d-9f2a9c3b7b8e

# Delete a customer by id
curl -X DELETE http://localhost:8080/api/customer/1f8c9a7c-6b7c-4f0e-8a1d-9f2a9c3b7b8e
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, we have shown you how to use PG middleware to connect Iris and PostgreSQL in a simple and type-safe way. We have also demonstrated how to use PG middleware features such as transactions, schema creation and validation, query tracing and error handling. We have also shown how to use PG middleware to create a simple REST API for managing customers.

We hope you find PG middleware useful and easy to use. If you have any feedback or questions, please feel free to open an issue or a pull request on GitHub. Thank you for reading!🙏

. . . . . . . . . . . . . . . . . . . . .
Terabox Video Player