Skip to content

Commit f7ff81b

Browse files
committed
add new PG middleware
1 parent 8051946 commit f7ff81b

File tree

8 files changed

+762
-1
lines changed

8 files changed

+762
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ Middleware is just a chain handlers which can be executed before or after the ma
3434

3535
| Middleware | Description | Example |
3636
| ----------------|-------------|-------------|
37-
| [jwt](jwt) | JSON Web Tokens | [jwt](jwt) |
37+
| [pg](pg) | PostgreSQL Database | [pg/_examples](pg/_examples/) |
38+
| [jwt](jwt) | JSON Web Tokens | [jwt/_example](jwt/_example/) |
3839
| [cors](cors) | HTTP Access Control. | [cors/_example](cors/_example) |
3940
| [secure](secure) | Middleware that implements a few quick security wins | [secure/_example](secure/_example/main.go) |
4041
| [tollbooth](tollboothic) | Generic middleware to rate-limit HTTP requests | [tollboothic/_examples/limit-handler](tollboothic/_examples/limit-handler) |

pg/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2023 Gerasimos Maropoulos <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

pg/README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# PG Middleware
2+
3+
PG middleware is a package for [Iris](https://iris-go.com/) web framework that provides easy and type-safe access to PostgreSQL database.
4+
5+
## Features
6+
7+
- Supports PostgreSQL 9.5 and above.
8+
- Uses [pg](https://github.com/kataras/pg) package and [pgx](https://github.com/jackc/pgx) driver under the hood.
9+
- Supports transactions, schema creation and validation, query tracing and error handling.
10+
- Allows registering custom types and table models using a schema object.
11+
- Provides a generic repository interface for common CRUD operations.
12+
13+
## Installation
14+
15+
To install PG middleware, use the following command:
16+
17+
```sh
18+
go get github.com/iris-contrib/middleware/pg@master
19+
```
20+
21+
## Usage
22+
23+
To use PG middleware, you need to:
24+
25+
1. Import the package in your code:
26+
27+
```go
28+
import (
29+
"github.com/kataras/iris/v12"
30+
31+
"github.com/iris-contrib/middleware/pg"
32+
)
33+
```
34+
35+
2. Define your database table models as structs with `json` and `pg` tags:
36+
37+
```go
38+
// The Customer database table model.
39+
type Customer struct {
40+
ID string `json:"id" pg:"type=uuid,primary"`
41+
Name string `json:"name" pg:"type=varchar(255)"`
42+
}
43+
```
44+
45+
3. Create a schema object and register your models:
46+
47+
```go
48+
schema := pg.NewSchema()
49+
schema.MustRegister("customers", Customer{})
50+
```
51+
52+
4. Create a PG middleware instance with the schema and database options:
53+
54+
```go
55+
opts := pg.Options{
56+
Host: "localhost",
57+
Port: 5432,
58+
User: "postgres",
59+
Password: "admin!123",
60+
DBName: "test_db",
61+
Schema: "public",
62+
SSLMode: "disable",
63+
Transactional: true, // or false to disable the transactional feature.
64+
Trace: true, // or false to production to disable query logging.
65+
CreateSchema: true, // true to create the schema if it doesn't exist.
66+
CheckSchema: true, // true to check the schema for missing tables and columns.
67+
ErrorHandler: func(ctx iris.Context, err error) {
68+
ctx.StopWithError(iris.StatusInternalServerError, err)
69+
},
70+
}
71+
72+
p := pg.New(schema, opts)
73+
```
74+
75+
5. Attach the middleware handler to your Iris app or routes:
76+
77+
```go
78+
app := iris.New()
79+
80+
postgresMiddleware := newPostgresMiddleware()
81+
82+
{
83+
customerAPI := app.Party("/api/customer", postgresMiddleware)
84+
customerAPI.Post("/", createCustomer)
85+
customerAPI.Get("/{id:uuid}", getCustomer)
86+
}
87+
```
88+
89+
6. Use the `pg.DB` or `pg.Repository` package-level functions to access the database instance or the repository interface in your handlers:
90+
91+
```go
92+
func createCustomer(ctx iris.Context) {
93+
var payload = struct {
94+
Name string `json:"name"`
95+
}{}
96+
err := ctx.ReadJSON(&payload)
97+
if err != nil {
98+
ctx.StopWithError(iris.StatusBadRequest, err)
99+
return
100+
}
101+
102+
// Get the current database instance through pg.DB middleware package-level function.
103+
// db := pg.DB(ctx)
104+
// [Work with db instance...]
105+
// OR, initialize a new repository of Customer type and work with it (type-safety).
106+
customers := pg.Repository[Customer](ctx)
107+
108+
// Insert a new Customer.
109+
customer := Customer{
110+
Name: payload.Name,
111+
}
112+
err = customers.InsertSingle(ctx, customer, &customer.ID)
113+
if err != nil {
114+
ctx.StopWithError(iris.StatusInternalServerError, err)
115+
return
116+
}
117+
118+
// Display the result ID.
119+
ctx.StatusCode(iris.StatusCreated)
120+
ctx.JSON(iris.Map{"id": customer.ID})
121+
}
122+
123+
func getCustomer(ctx iris.Context) {
124+
// Get the id from the path parameter.
125+
id := ctx.Params().Get("id")
126+
127+
// Get the repository of Customer type through pg.Repository middleware package-level function.
128+
customers := pg.Repository[Customer](ctx)
129+
130+
// Get the customer by the id.
131+
customer, err := customers.SelectByID(ctx, id)
132+
if err != nil {
133+
if pg.IsErrNoRows(err) {
134+
ctx.StopWithStatus(iris.StatusNotFound)
135+
} else {
136+
ctx.StopWithError(iris.StatusInternalServerError, err)
137+
}
138+
139+
return
140+
}
141+
142+
// Display the retrieved Customer.
143+
ctx.JSON(customer)
144+
}
145+
```
146+
147+
## Examples
148+
149+
You can find more examples of using PG middleware in the [examples](./examples) folder.
150+
151+
## License
152+
153+
PG middleware is licensed under the [MIT License](./LICENSE).

pg/_examples/basic/main.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package main
2+
3+
import (
4+
"github.com/kataras/iris/v12"
5+
6+
"github.com/iris-contrib/middleware/pg"
7+
)
8+
9+
// The Customer database table model.
10+
type Customer struct {
11+
ID string `json:"id" pg:"type=uuid,primary"`
12+
Name string `json:"name" pg:"type=varchar(255)"`
13+
}
14+
15+
func main() {
16+
app := iris.New()
17+
18+
postgresMiddleware := newPostgresMiddleware()
19+
20+
{
21+
customerAPI := app.Party("/api/customer", postgresMiddleware)
22+
customerAPI.Post("/", createCustomer)
23+
customerAPI.Get("/{id:uuid}", getCustomer)
24+
}
25+
26+
/*
27+
Create Customer:
28+
29+
curl --location 'http://localhost:8080/api/customer' \
30+
--header 'Content-Type: application/json' \
31+
--data '{"name": "Gerasimos"}'
32+
33+
Response:
34+
{
35+
"id": "a2657a3c-e5f7-43f8-adae-01bca01b3325"
36+
}
37+
38+
Get Customer by ID:
39+
40+
curl --location 'http://localhost:8080/api/customer/a2657a3c-e5f7-43f8-adae-01bca01b3325'
41+
42+
Response:
43+
{
44+
"id": "a2657a3c-e5f7-43f8-adae-01bca01b3325",
45+
"name": "Gerasimos"
46+
}
47+
48+
*/
49+
app.Listen(":8080")
50+
}
51+
52+
func createCustomer(ctx iris.Context) {
53+
var payload = struct {
54+
Name string `json:"name"`
55+
}{}
56+
err := ctx.ReadJSON(&payload)
57+
if err != nil {
58+
ctx.StopWithError(iris.StatusBadRequest, err)
59+
return
60+
}
61+
62+
// Get the current database instance through pg.DB middleware package-level function.
63+
// db := pg.DB(ctx)
64+
// [Work with db instance...]
65+
// OR, initialize a new repository of Customer type and work with it (type-safety).
66+
customers := pg.Repository[Customer](ctx)
67+
68+
// Insert a new Customer.
69+
customer := Customer{
70+
Name: payload.Name,
71+
}
72+
err = customers.InsertSingle(ctx, customer, &customer.ID)
73+
if err != nil {
74+
ctx.StopWithError(iris.StatusInternalServerError, err)
75+
return
76+
}
77+
78+
// Display the result ID.
79+
ctx.StatusCode(iris.StatusCreated)
80+
ctx.JSON(iris.Map{"id": customer.ID})
81+
}
82+
83+
func getCustomer(ctx iris.Context) {
84+
// Get the id from the path parameter.
85+
id := ctx.Params().Get("id")
86+
87+
// Get the repository of Customer type through pg.Repository middleware package-level function.
88+
customers := pg.Repository[Customer](ctx)
89+
90+
// Get the customer by the id.
91+
customer, err := customers.SelectByID(ctx, id)
92+
if err != nil {
93+
if pg.IsErrNoRows(err) {
94+
ctx.StopWithStatus(iris.StatusNotFound)
95+
} else {
96+
ctx.StopWithError(iris.StatusInternalServerError, err)
97+
}
98+
99+
return
100+
}
101+
102+
// Display the retrieved Customer.
103+
ctx.JSON(customer)
104+
}
105+
106+
func newPostgresMiddleware() iris.Handler {
107+
schema := pg.NewSchema()
108+
schema.MustRegister("customers", Customer{})
109+
110+
opts := pg.Options{
111+
Host: "localhost",
112+
Port: 5432,
113+
User: "postgres",
114+
Password: "admin!123",
115+
DBName: "test_db",
116+
Schema: "public",
117+
SSLMode: "disable",
118+
Transactional: true, // or false to disable the transactional feature.
119+
Trace: true, // or false to production to disable query logging.
120+
CreateSchema: true, // true to create the schema if it doesn't exist.
121+
CheckSchema: true, // true to check the schema for missing tables and columns.
122+
ErrorHandler: func(ctx iris.Context, err error) {
123+
ctx.StopWithError(iris.StatusInternalServerError, err)
124+
},
125+
}
126+
127+
p := pg.New(schema, opts)
128+
return p.Handler()
129+
}

pg/aliases.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pg
2+
3+
import (
4+
"errors"
5+
6+
"github.com/kataras/pg"
7+
)
8+
9+
// NewSchema returns a new Schema instance, it's a shortcut of pg.NewSchema.
10+
var NewSchema = pg.NewSchema
11+
12+
// ErrNoRows is a type alias of pg.ErrNoRows.
13+
var ErrNoRows = pg.ErrNoRows
14+
15+
// IsErrNoRows reports whether the error is of type pg.ErrNoRows.
16+
func IsErrNoRows(err error) bool {
17+
if err == nil {
18+
return false
19+
}
20+
21+
return errors.Is(err, ErrNoRows)
22+
}

0 commit comments

Comments
 (0)