Repository: deepmap/oapi-codegen
Pull request #51 (2019-08-19 22:57:37)

Generate chi based http handler
This is a first pass at adding [chi](https://github.com/go-chi/chi) support following on from https://github.com/deepmap/oapi-codegen/issues/2 to generate an alternative to the echo based routing. There's still a bunch of missing pieces but I wanted to push it up early for general feels. 

### Why?
The current http handler solution is built around `echo` and requires the calling application to bring along this dependency. `Chi `is "100% compatible with net/http" making it self-contained and simple to integrate with existing go applications whilst also leaning heavily into the core `context` API making it more "go-like".

### Example
Here's an example of how you could go about registering your operations. You'll note that there's no peer dependency on `chi` as `ChiHandler` returns a `http.Handler`.

Implementation is definitely a little verbose having raw request, response and passing around `Context` but I think the http.Handler interface will be more flexible in the long run and should be familiar for most go developers.

```go
type Operations struct {}

func (*Operations) FindPets(w http.ResponseWriter, r *http.Request) {
  params := petstore.ParamsForFindPets(r.Context())
  ...
}

func (*Operations) DeletePet(w http.ResponseWriter, r *http.Request) {
  params := petstore.ParamsForFindPets(r.Context())
  ...
}

func (*Operations) AddPet(w http.ResponseWriter, r *http.Request) {
  ...
}

func (*Operations) FindPetById(w http.ResponseWriter, r *http.Request) {
  ...
}

func Handler() {
  myApi := Operations{}
  petstore.ChiHandler(myApi)
}

```

### Questions
* Is this something you're open to incorporating?! 😁 
* Rename `ChiHandler` to `Handler`? Chi is really an implementation detail that doesn't need to be exposed externally.

### TODO:
* [x] Separate out templates from existing echo based ones.
* [x] Add new flag for generating chi http handler
* [X] Add example implementation
* [x] Add some test coverage! 

deepmap-marcinr: I've been thinking about this, and it's hard to do it cleanly, but your approach looks pretty nice. 

Right now, we have a fair bit of code that's router agonstic (all the type generation), and what's echo specific is the server interface (the client interface is agnostic). So, I would refactor the functionality so that the entirety of Echo bindings are in, say, echo-serverinterface.tmpl and echo-wrappers.tmpl, and you could produce chi-specific templates. You would select the backend via a command line option, which would substitute in chi-whatever.tmpl in place of those two files.

I initially chose Echo because it's quick, and it simplifies a lot of stuff, and it works for us, but I'm not particularly attached it to one way or another.

deepmap-marcinr: One practical thing that I've found is that it's good to have an interface to implement in your generated code - for mocking, or whatever. So your operations{} struct might want to implement an interface.

I'm totally open to incorporating this, if we can constrain the chi-specific code to a few .tmpl files, just as we've done with Echo.
dannymidnight: Great. I'll try clean things up a bit and let you know when it's ready for review.
dannymidnight: > One practical thing that I've found is that it's good to have an interface to implement in your generated code - for mocking, or whatever. So your operations{} struct might want to implement an interface.

Yep makes sense, in my petstore example the `operations` struct is implementing [this interface](https://github.com/deepmap/oapi-codegen/pull/51/files#diff-0965434705f04f2955ae8c6b4b7928dfR32-R41).






dannymidnight: Hey @deepmap-marcinr I think this is now ready for review. 

Major changes since last time:

* There's now a `chi-server` flag for generating the new implementation and I've kept the echo codepath using `server` for backwards compatibility.
* I've added an example implementation and test runner following a similar approach to the `echo` based tests.
* I've split the petstore examples into the following structure with the client split out from the server implementations:
```
- petstore
-- chi
-- echo
-- petstore-client.gen.go
```

Let me know what you think! Any and all feedback welcome.



Patch:
diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go
index 6ec7895..53f0904 100644
--- a/cmd/oapi-codegen/oapi-codegen.go
+++ b/cmd/oapi-codegen/oapi-codegen.go
@@ -62,8 +62,10 @@ func main() {
 		switch g {
 		case "client":
 			opts.GenerateClient = true
+		case "chi-server":
+			opts.GenerateChiServer = true
 		case "server":
-			opts.GenerateServer = true
+			opts.GenerateEchoServer = true
 		case "types":
 			opts.GenerateTypes = true
 		case "spec":
diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go
new file mode 100644
index 0000000..f7fe0e2
--- /dev/null
+++ b/examples/petstore-expanded/chi/api/petstore.gen.go
@@ -0,0 +1,233 @@
+// Package api provides primitives to interact the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT.
+package api
+
+import (
+	"bytes"
+	"compress/gzip"
+	"context"
+	"encoding/base64"
+	"fmt"
+	"github.com/deepmap/oapi-codegen/pkg/runtime"
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/go-chi/chi"
+	"net/http"
+	"strings"
+)
+
+// Error defines model for Error.
+type Error struct {
+	Code    int32  `json:"code"`
+	Message string `json:"message"`
+}
+
+// NewPet defines model for NewPet.
+type NewPet struct {
+	Name string  `json:"name"`
+	Tag  *string `json:"tag,omitempty"`
+}
+
+// Pet defines model for Pet.
+type Pet struct {
+	// Embedded struct due to allOf(#/components/schemas/NewPet)
+	NewPet
+	// Embedded fields due to inline allOf schema
+	Id int64 `json:"id"`
+}
+
+// FindPetsParams defines parameters for FindPets.
+type FindPetsParams struct {
+	Tags  *[]string `json:"tags,omitempty"`
+	Limit *int32    `json:"limit,omitempty"`
+}
+
+// addPetJSONBody defines parameters for AddPet.
+type addPetJSONBody NewPet
+
+// AddPetRequestBody defines body for AddPet for application/json ContentType.
+type AddPetJSONRequestBody addPetJSONBody
+
+type ServerInterface interface {
+	//  (GET /pets)
+	FindPets(w http.ResponseWriter, r *http.Request)
+	//  (POST /pets)
+	AddPet(w http.ResponseWriter, r *http.Request)
+	//  (DELETE /pets/{id})
+	DeletePet(w http.ResponseWriter, r *http.Request)
+	//  (GET /pets/{id})
+	FindPetById(w http.ResponseWriter, r *http.Request)
+}
+
+// ParamsForFindPets operation parameters from context
+func ParamsForFindPets(ctx context.Context) *FindPetsParams {
+	return ctx.Value("FindPetsParams").(*FindPetsParams)
+}
+
+// FindPets operation middleware
+func FindPetsCtx(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ctx := r.Context()
+
+		var err error
+		// Parameter object where we will unmarshal all parameters from the context
+		var params FindPetsParams
+
+		// ------------- Optional query parameter "tags" -------------
+		if paramValue := r.URL.Query().Get("tags"); paramValue != "" {
+
+		}
+
+		err = runtime.BindQueryParameter("form", true, false, "tags", r.URL.Query(), &params.Tags)
+		if err != nil {
+			http.Error(w, fmt.Sprintf("Invalid format for parameter tags: %s", err), http.StatusBadRequest)
+			return
+		}
+
+		// ------------- Optional query parameter "limit" -------------
+		if paramValue := r.URL.Query().Get("limit"); paramValue != "" {
+
+		}
+
+		err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), &params.Limit)
+		if err != nil {
+			http.Error(w, fmt.Sprintf("Invalid format for parameter limit: %s", err), http.StatusBadRequest)
+			return
+		}
+
+		ctx = context.WithValue(r.Context(), "FindPetsParams", &params)
+
+		next.ServeHTTP(w, r.WithContext(ctx))
+	})
+}
+
+// AddPet operation middleware
+func AddPetCtx(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ctx := r.Context()
+
+		next.ServeHTTP(w, r.WithContext(ctx))
+	})
+}
+
+// DeletePet operation middleware
+func DeletePetCtx(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ctx := r.Context()
+
+		// ------------- Path parameter "id" -------------
+		var pathErr error
+		var id int64
+
+		pathErr = runtime.BindStyledParameter("simple", false, "id", chi.URLParam(r, "id"), &id)
+		if pathErr != nil {
+			http.Error(w, fmt.Sprintf("Invalid format for parameter id: %s", pathErr), http.StatusBadRequest)
+			return
+		}
+
+		ctx = context.WithValue(r.Context(), "id", id)
+
+		next.ServeHTTP(w, r.WithContext(ctx))
+	})
+}
+
+// FindPetById operation middleware
+func FindPetByIdCtx(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		ctx := r.Context()
+
+		// ------------- Path parameter "id" -------------
+		var pathErr error
+		var id int64
+
+		pathErr = runtime.BindStyledParameter("simple", false, "id", chi.URLParam(r, "id"), &id)
+		if pathErr != nil {
+			http.Error(w, fmt.Sprintf("Invalid format for parameter id: %s", pathErr), http.StatusBadRequest)
+			return
+		}
+
+		ctx = context.WithValue(r.Context(), "id", id)
+
+		next.ServeHTTP(w, r.WithContext(ctx))
+	})
+}
+
+// Handler creates http.Handler with routing matching OpenAPI spec.
+func Handler(si ServerInterface) http.Handler {
+	r := chi.NewRouter()
+
+	r.Group(func(r chi.Router) {
+		r.Use(FindPetsCtx)
+		r.Get("/pets", si.FindPets)
+	})
+	r.Group(func(r chi.Router) {
+		r.Use(AddPetCtx)
+		r.Post("/pets", si.AddPet)
+	})
+	r.Group(func(r chi.Router) {
+		r.Use(DeletePetCtx)
+		r.Delete("/pets/{id}", si.DeletePet)
+	})
+	r.Group(func(r chi.Router) {
+		r.Use(FindPetByIdCtx)
+		r.Get("/pets/{id}", si.FindPetById)
+	})
+
+	return r
+}
+
+// Base64 encoded, gzipped, json marshaled Swagger object
+var swaggerSpec = []string{
+
+	"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",
+}
+
+// GetSwagger returns the Swagger specification corresponding to the generated code
+// in this file.
+func GetSwagger() (*openapi3.Swagger, error) {
+	zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
+	if err != nil {
+		return nil, fmt.Errorf("error base64 decoding spec: %s", err)
+	}
+	zr, err := gzip.NewReader(bytes.NewReader(zipped))
+	if err != nil {
+		return nil, fmt.Errorf("error decompressing spec: %s", err)
+	}
+	var buf bytes.Buffer
+	_, err = buf.ReadFrom(zr)
+	if err != nil {
+		return nil, fmt.Errorf("error decompressing spec: %s", err)
+	}
+
+	swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(buf.Bytes())
+	if err != nil {
+		return nil, fmt.Errorf("error loading Swagger: %s", err)
+	}
+	return swagger, nil
+}
diff --git a/examples/petstore-expanded/chi/api/petstore.go b/examples/petstore-expanded/chi/api/petstore.go
new file mode 100644
index 0000000..a7e2358
--- /dev/null
+++ b/examples/petstore-expanded/chi/api/petstore.go
@@ -0,0 +1,130 @@
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types,chi-server,spec -o petstore.gen.go ../../petstore-expanded.yaml
+
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"sync"
+)
+
+type PetStore struct {
+	Pets   map[int64]Pet
+	NextId int64
+	Lock   sync.Mutex
+}
+
+func NewPetStore() *PetStore {
+	return &PetStore{
+		Pets:   make(map[int64]Pet),
+		NextId: 1000,
+	}
+}
+
+// This function wraps sending of an error in the Error format, and
+// handling the failure to marshal that.
+func sendPetstoreError(w http.ResponseWriter, code int, message string) {
+	petErr := Error{
+		Code:    int32(code),
+		Message: message,
+	}
+	w.WriteHeader(code)
+	json.NewEncoder(w).Encode(petErr)
+}
+
+// Here, we implement all of the handlers in the ServerInterface
+func (p *PetStore) FindPets(w http.ResponseWriter, r *http.Request) {
+	params := ParamsForFindPets(r.Context())
+
+	p.Lock.Lock()
+	defer p.Lock.Unlock()
+
+	var result []Pet
+
+	for _, pet := range p.Pets {
+		if params.Tags != nil {
+			// If we have tags,  filter pets by tag
+			for _, t := range *params.Tags {
+				if pet.Tag != nil && (*pet.Tag == t) {
+					result = append(result, pet)
+				}
+			}
+		} else {
+			// Add all pets if we're not filtering
+			result = append(result, pet)
+		}
+
+		if params.Limit != nil {
+			l := int(*params.Limit)
+			if len(result) >= l {
+				// We're at the limit
+				break
+			}
+		}
+	}
+
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(result)
+}
+
+func (p *PetStore) AddPet(w http.ResponseWriter, r *http.Request) {
+	// We expect a NewPet object in the request body.
+	var newPet NewPet
+	if err := json.NewDecoder(r.Body).Decode(&newPet); err != nil {
+		sendPetstoreError(w, http.StatusBadRequest, "Invalid format for NewPet")
+		return
+	}
+
+	// We now have a pet, let's add it to our "database".
+
+	// We're always asynchronous, so lock unsafe operations below
+	p.Lock.Lock()
+	defer p.Lock.Unlock()
+
+	// We handle pets, not NewPets, which have an additional ID field
+	var pet Pet
+	pet.Name = newPet.Name
+	pet.Tag = newPet.Tag
+	pet.Id = p.NextId
+	p.NextId = p.NextId + 1
+
+	// Insert into map
+	p.Pets[pet.Id] = pet
+
+	// Now, we have to return the NewPet
+	w.WriteHeader(http.StatusCreated)
+	json.NewEncoder(w).Encode(pet)
+}
+
+func (p *PetStore) FindPetById(w http.ResponseWriter, r *http.Request) {
+	id := r.Context().Value("id").(int64)
+
+	p.Lock.Lock()
+	defer p.Lock.Unlock()
+
+	pet, found := p.Pets[id]
+	if !found {
+		sendPetstoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id))
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(pet)
+}
+
+func (p *PetStore) DeletePet(w http.ResponseWriter, r *http.Request) {
+	id := r.Context().Value("id").(int64)
+
+	p.Lock.Lock()
+	defer p.Lock.Unlock()
+
+	_, found := p.Pets[id]
+	if !found {
+		sendPetstoreError(w, http.StatusNotFound, fmt.Sprintf("Could not find pet with ID %d", id))
+		return
+	}
+	delete(p.Pets, id)
+
+	w.WriteHeader(http.StatusNoContent)
+}
diff --git a/examples/petstore-expanded/chi/petstore.go b/examples/petstore-expanded/chi/petstore.go
new file mode 100644
index 0000000..9cd6b9e
--- /dev/null
+++ b/examples/petstore-expanded/chi/petstore.go
@@ -0,0 +1,33 @@
+// This is an example of implementing the Pet Store from the OpenAPI documentation
+// found at:
+// https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore.yaml
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"net/http"
+
+	api "github.com/deepmap/oapi-codegen/examples/petstore-expanded/chi/api"
+)
+
+func main() {
+	var port = flag.Int("port", 8080, "Port for test HTTP server")
+	flag.Parse()
+
+	// Create an instance of our handler which satisfies the generated interface
+	petStore := api.NewPetStore()
+
+	// We now register our petStore above as the handler for the interface
+	h := api.Handler(petStore)
+
+	s := &http.Server{
+		Handler: h,
+		Addr:    fmt.Sprintf("0.0.0.0:%d", port),
+	}
+
+	// And we serve HTTP until the world ends.
+	log.Fatal(s.ListenAndServe())
+}
diff --git a/examples/petstore-expanded/api/petstore-server.gen.go b/examples/petstore-expanded/echo/api/petstore-server.gen.go
similarity index 100%
rename from examples/petstore-expanded/api/petstore-server.gen.go
rename to examples/petstore-expanded/echo/api/petstore-server.gen.go
diff --git a/examples/petstore-expanded/api/petstore-types.gen.go b/examples/petstore-expanded/echo/api/petstore-types.gen.go
similarity index 100%
rename from examples/petstore-expanded/api/petstore-types.gen.go
rename to examples/petstore-expanded/echo/api/petstore-types.gen.go
diff --git a/examples/petstore-expanded/internal/petstore.go b/examples/petstore-expanded/echo/api/petstore.go
similarity index 83%
rename from examples/petstore-expanded/internal/petstore.go
rename to examples/petstore-expanded/echo/api/petstore.go
index 9611dfb..74caaf0 100644
--- a/examples/petstore-expanded/internal/petstore.go
+++ b/examples/petstore-expanded/echo/api/petstore.go
@@ -11,34 +11,37 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package internal
+
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types -o petstore-types.gen.go ../../petstore-expanded.yaml
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate server,spec -o petstore-server.gen.go ../../petstore-expanded.yaml
+
+package api
 
 import (
 	"fmt"
-	"github.com/labstack/echo/v4"
 	"net/http"
 	"sync"
 
-	"github.com/deepmap/oapi-codegen/examples/petstore-expanded/api"
+	"github.com/labstack/echo/v4"
 )
 
 type PetStore struct {
-	Pets   map[int64]api.Pet
+	Pets   map[int64]Pet
 	NextId int64
 	Lock   sync.Mutex
 }
 
 func NewPetStore() *PetStore {
 	return &PetStore{
-		Pets:   make(map[int64]api.Pet),
+		Pets:   make(map[int64]Pet),
 		NextId: 1000,
 	}
 }
 
-// This function wraps sending of an error in the api.Error format, and
+// This function wraps sending of an error in the Error format, and
 // handling the failure to marshal that.
 func sendPetstoreError(ctx echo.Context, code int, message string) error {
-	petErr := api.Error{
+	petErr := Error{
 		Code:    int32(code),
 		Message: message,
 	}
@@ -46,12 +49,12 @@ func sendPetstoreError(ctx echo.Context, code int, message string) error {
 	return err
 }
 
-// Here, we implement all of the handlers in the api.ServerInterface
-func (p *PetStore) FindPets(ctx echo.Context, params api.FindPetsParams) error {
+// Here, we implement all of the handlers in the ServerInterface
+func (p *PetStore) FindPets(ctx echo.Context, params FindPetsParams) error {
 	p.Lock.Lock()
 	defer p.Lock.Unlock()
 
-	var result []api.Pet
+	var result []Pet
 
 	for _, pet := range p.Pets {
 		if params.Tags != nil {
@@ -79,7 +82,7 @@ func (p *PetStore) FindPets(ctx echo.Context, params api.FindPetsParams) error {
 
 func (p *PetStore) AddPet(ctx echo.Context) error {
 	// We expect a NewPet object in the request body.
-	var newPet api.NewPet
+	var newPet NewPet
 	err := ctx.Bind(&newPet)
 	if err != nil {
 		return sendPetstoreError(ctx, http.StatusBadRequest, "Invalid format for NewPet")
@@ -91,7 +94,7 @@ func (p *PetStore) AddPet(ctx echo.Context) error {
 	defer p.Lock.Unlock()
 
 	// We handle pets, not NewPets, which have an additional ID field
-	var pet api.Pet
+	var pet Pet
 	pet.Name = newPet.Name
 	pet.Tag = newPet.Tag
 	pet.Id = p.NextId
diff --git a/examples/petstore-expanded/cmd/petstore/petstore.go b/examples/petstore-expanded/echo/petstore.go
similarity index 88%
rename from examples/petstore-expanded/cmd/petstore/petstore.go
rename to examples/petstore-expanded/echo/petstore.go
index d710e13..af0ae83 100644
--- a/examples/petstore-expanded/cmd/petstore/petstore.go
+++ b/examples/petstore-expanded/echo/petstore.go
@@ -10,8 +10,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/deepmap/oapi-codegen/examples/petstore-expanded/api"
-	"github.com/deepmap/oapi-codegen/examples/petstore-expanded/internal"
+	"github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api"
 	"github.com/deepmap/oapi-codegen/pkg/middleware"
 	"github.com/labstack/echo/v4"
 	echomiddleware "github.com/labstack/echo/v4/middleware"
@@ -32,7 +31,7 @@ func main() {
 	swagger.Servers = nil
 
 	// Create an instance of our handler which satisfies the generated interface
-	petStore := internal.NewPetStore()
+	petStore := api.NewPetStore()
 
 	// This is how you set up a basic Echo router
 	e := echo.New()
diff --git a/examples/petstore-expanded/api/doc.go b/examples/petstore-expanded/internal/doc.go
similarity index 68%
rename from examples/petstore-expanded/api/doc.go
rename to examples/petstore-expanded/internal/doc.go
index 307583b..ac961b7 100644
--- a/examples/petstore-expanded/api/doc.go
+++ b/examples/petstore-expanded/internal/doc.go
@@ -11,12 +11,10 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package api
+package internal
 
 // This directory contains the OpenAPI 3.0 specification which defines our
 // server. The file petstore.gen.go is automatically generated from the schema
 
 // Run oapi-codegen to regenerate the petstore boilerplate
-//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate types -o petstore-types.gen.go petstore-expanded.yaml
-//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate server,spec -o petstore-server.gen.go petstore-expanded.yaml
-//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=api --generate client -o petstore-client.gen.go petstore-expanded.yaml
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=petstore --generate types,client -o ../petstore-client.gen.go ../petstore-expanded.yaml
diff --git a/examples/petstore-expanded/api/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go
similarity index 93%
rename from examples/petstore-expanded/api/petstore-client.gen.go
rename to examples/petstore-expanded/petstore-client.gen.go
index b6bc132..fb1433c 100644
--- a/examples/petstore-expanded/api/petstore-client.gen.go
+++ b/examples/petstore-expanded/petstore-client.gen.go
@@ -1,7 +1,7 @@
-// Package api provides primitives to interact the openapi HTTP API.
+// Package petstore provides primitives to interact the openapi HTTP API.
 //
 // Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT.
-package api
+package petstore
 
 import (
 	"bytes"
@@ -15,6 +15,38 @@ import (
 	"strings"
 )
 
+// Error defines model for Error.
+type Error struct {
+	Code    int32  `json:"code"`
+	Message string `json:"message"`
+}
+
+// NewPet defines model for NewPet.
+type NewPet struct {
+	Name string  `json:"name"`
+	Tag  *string `json:"tag,omitempty"`
+}
+
+// Pet defines model for Pet.
+type Pet struct {
+	// Embedded struct due to allOf(#/components/schemas/NewPet)
+	NewPet
+	// Embedded fields due to inline allOf schema
+	Id int64 `json:"id"`
+}
+
+// FindPetsParams defines parameters for FindPets.
+type FindPetsParams struct {
+	Tags  *[]string `json:"tags,omitempty"`
+	Limit *int32    `json:"limit,omitempty"`
+}
+
+// addPetJSONBody defines parameters for AddPet.
+type addPetJSONBody NewPet
+
+// AddPetRequestBody defines body for AddPet for application/json ContentType.
+type AddPetJSONRequestBody addPetJSONBody
+
 // RequestEditorFn  is the function signature for the RequestEditor callback function
 type RequestEditorFn func(req *http.Request, ctx context.Context) error
 
diff --git a/examples/petstore-expanded/api/petstore-expanded.yaml b/examples/petstore-expanded/petstore-expanded.yaml
similarity index 100%
rename from examples/petstore-expanded/api/petstore-expanded.yaml
rename to examples/petstore-expanded/petstore-expanded.yaml
diff --git a/go.mod b/go.mod
index d5a5e8a..f1ae5ce 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/deepmap/oapi-codegen
 require (
 	github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c
 	github.com/getkin/kin-openapi v0.2.0
+	github.com/go-chi/chi v4.0.2+incompatible
 	github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219
 	github.com/labstack/echo/v4 v4.1.6
 	github.com/pkg/errors v0.8.1
diff --git a/go.sum b/go.sum
index a8390c0..b1b32cd 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,8 @@ github.com/getkin/kin-openapi v0.2.0 h1:PbHHtYZpjKwZtGlIyELgA2DploRrsaXztoNNx9Hj
 github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
+github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4=
 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index 49f039f..f030dff 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -31,10 +31,11 @@ import (
 
 // Options defines the optional code to generate.
 type Options struct {
-	GenerateServer bool // GenerateServer specifies whether to generate server boilerplate
-	GenerateClient bool // GenerateClient specifies whether to generate client boilerplate
-	GenerateTypes  bool // GenerateTypes specifies whether to generate type definitions
-	EmbedSpec      bool // Whether to embed the swagger spec in the generated code
+	GenerateChiServer  bool // GenerateChiServer specifies whether to generate chi server boilerplate
+	GenerateEchoServer bool // GenerateEchoServer specifies whether to generate echo server boilerplate
+	GenerateClient     bool // GenerateClient specifies whether to generate client boilerplate
+	GenerateTypes      bool // GenerateTypes specifies whether to generate type definitions
+	EmbedSpec          bool // Whether to embed the swagger spec in the generated code
 }
 
 // Uses the Go templating engine to generate all of our server wrappers from
@@ -63,9 +64,17 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri
 		}
 	}
 
-	var serverOut string
-	if opts.GenerateServer {
-		serverOut, err = GenerateServer(t, ops)
+	var echoServerOut string
+	if opts.GenerateEchoServer {
+		echoServerOut, err = GenerateEchoServer(t, ops)
+		if err != nil {
+			return "", errors.Wrap(err, "error generating Go handlers for Paths")
+		}
+	}
+
+	var chiServerOut string
+	if opts.GenerateChiServer {
+		chiServerOut, err = GenerateChiServer(t, ops)
 		if err != nil {
 			return "", errors.Wrap(err, "error generating Go handlers for Paths")
 		}
@@ -103,7 +112,7 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri
 
 	// Based on module prefixes, figure out which optional imports are required.
 	// TODO: this is error prone, use tighter matches
-	for _, str := range []string{typeDefinitions, serverOut, clientOut, clientWithResponsesOut, inlinedSpec} {
+	for _, str := range []string{typeDefinitions, chiServerOut, echoServerOut, clientOut, clientWithResponsesOut, inlinedSpec} {
 		if strings.Contains(str, "time.Time") {
 			imports = append(imports, "time")
 		}
@@ -161,6 +170,9 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri
 		if strings.Contains(str, "errors.") {
 			imports = append(imports, "github.com/pkg/errors")
 		}
+		if strings.Contains(str, "chi.") {
+			imports = append(imports, "github.com/go-chi/chi")
+		}
 	}
 
 	importsOut, err := GenerateImports(t, imports, packageName)
@@ -190,8 +202,15 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri
 		}
 	}
 
-	if opts.GenerateServer {
-		_, err = w.WriteString(serverOut)
+	if opts.GenerateEchoServer {
+		_, err = w.WriteString(echoServerOut)
+		if err != nil {
+			return "", errors.Wrap(err, "error writing server path handlers")
+		}
+	}
+
+	if opts.GenerateChiServer {
+		_, err = w.WriteString(chiServerOut)
 		if err != nil {
 			return "", errors.Wrap(err, "error writing server path handlers")
 		}
diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go
index 227ea28..880315d 100644
--- a/pkg/codegen/operations.go
+++ b/pkg/codegen/operations.go
@@ -612,9 +612,38 @@ func GenerateTypesForOperations(t *template.Template, ops []OperationDefinition)
 	return buf.String(), nil
 }
 
-// This function generates all the go code for the ServerInterface as well as
+// GenerateChiServer This function generates all the go code for the ServerInterface as well as
 // all the wrapper functions around our handlers.
-func GenerateServer(t *template.Template, operations []OperationDefinition) (string, error) {
+func GenerateChiServer(t *template.Template, operations []OperationDefinition) (string, error) {
+	var buf bytes.Buffer
+	w := bufio.NewWriter(&buf)
+
+	err := t.ExecuteTemplate(w, "chi-interface.tmpl", operations)
+	if err != nil {
+		return "", errors.Wrap(err, "error generating server interface")
+	}
+
+	err = t.ExecuteTemplate(w, "chi-middleware.tmpl", operations)
+	if err != nil {
+		return "", errors.Wrap(err, "error generating server middleware")
+	}
+
+	err = t.ExecuteTemplate(w, "chi-handler.tmpl", operations)
+	if err != nil {
+		return "", errors.Wrap(err, "error generating server http handler")
+	}
+
+	err = w.Flush()
+	if err != nil {
+		return "", errors.Wrap(err, "error flushing output buffer for server")
+	}
+
+	return buf.String(), nil
+}
+
+// GenerateEchoServer This function generates all the go code for the ServerInterface as well as
+// all the wrapper functions around our handlers.
+func GenerateEchoServer(t *template.Template, operations []OperationDefinition) (string, error) {
 	si, err := GenerateServerInterface(t, operations)
 	if err != nil {
 		return "", fmt.Errorf("Error generating server types and interface: %s", err)
diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go
index 23dbf2d..1d8bffd 100644
--- a/pkg/codegen/template_helpers.go
+++ b/pkg/codegen/template_helpers.go
@@ -249,6 +249,7 @@ var TemplateFunctions = template.FuncMap{
 	"genParamNames":              genParamNames,
 	"genParamFmtString":          genParamFmtString,
 	"swaggerUriToEchoUri":        SwaggerUriToEchoUri,
+	"swaggerUriToChiUri":         SwaggerUriToChiUri,
 	"lcFirst":                    LowercaseFirstCharacter,
 	"camelCase":                  ToCamelCase,
 	"genResponsePayload":         genResponsePayload,
@@ -256,4 +257,6 @@ var TemplateFunctions = template.FuncMap{
 	"genResponseUnmarshal":       genResponseUnmarshal,
 	"getResponseTypeDefinitions": getResponseTypeDefinitions,
 	"toStringArray":              toStringArray,
+	"lower":                      strings.ToLower,
+	"title":                      strings.Title,
 }
diff --git a/pkg/codegen/templates/chi-handler.tmpl b/pkg/codegen/templates/chi-handler.tmpl
new file mode 100644
index 0000000..335a1e6
--- /dev/null
+++ b/pkg/codegen/templates/chi-handler.tmpl
@@ -0,0 +1,11 @@
+// Handler creates http.Handler with routing matching OpenAPI spec.
+func Handler(si ServerInterface) http.Handler {
+  r := chi.NewRouter()
+
+{{range .}}r.Group(func(r chi.Router) {
+  r.Use({{.OperationId}}Ctx)
+  r.{{.Method | lower | title }}("{{.Path | swaggerUriToChiUri}}", si.{{.OperationId}})
+})
+{{end}}
+  return r
+}
diff --git a/pkg/codegen/templates/chi-interface.tmpl b/pkg/codegen/templates/chi-interface.tmpl
new file mode 100644
index 0000000..e315083
--- /dev/null
+++ b/pkg/codegen/templates/chi-interface.tmpl
@@ -0,0 +1,5 @@
+type ServerInterface interface {
+{{range .}}// {{.Summary}} ({{.Method}} {{.Path}})
+{{.OperationId}}(w http.ResponseWriter, r *http.Request)
+{{end}}
+}
diff --git a/pkg/codegen/templates/chi-middleware.tmpl b/pkg/codegen/templates/chi-middleware.tmpl
new file mode 100644
index 0000000..1cf061f
--- /dev/null
+++ b/pkg/codegen/templates/chi-middleware.tmpl
@@ -0,0 +1,170 @@
+
+{{range .}}{{$opid := .OperationId}}
+
+{{if .RequiresParamObject}}
+// ParamsFor{{.OperationId}} operation parameters from context
+func ParamsFor{{.OperationId}}(ctx context.Context) *{{.OperationId}}Params {
+  return ctx.Value("{{.OperationId}}Params").(*{{.OperationId}}Params)
+}
+{{end}}
+
+// {{$opid}} operation middleware
+func {{$opid}}Ctx(next http.Handler) http.Handler {
+  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+    ctx := r.Context()
+
+    {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
+    var pathErr error
+    var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
+
+    {{if .IsPassThrough}}
+    {{$varName}} = chi.URLParam(r, "{{.ParamName}}")
+    {{end}}
+    {{if .IsJson}}
+    pathErr = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}})
+    if pathErr != nil {
+      http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest)
+      return
+    }
+    {{end}}
+    {{if .IsStyled}}
+    pathErr = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", chi.URLParam(r, "{{.ParamName}}"), &{{$varName}})
+    if pathErr != nil {
+      http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", pathErr), http.StatusBadRequest)
+      return
+    }
+    {{end}}
+
+    ctx = context.WithValue(r.Context(), "{{$varName}}", {{$varName}})
+    {{end}}
+
+    {{if .RequiresParamObject}}
+      var err error
+      // Parameter object where we will unmarshal all parameters from the context
+      var params {{.OperationId}}Params
+
+      {{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
+        if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
+
+        {{if .IsPassThrough}}
+          params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
+        {{end}}
+
+        {{if .IsJson}}
+          var value {{.TypeDef}}
+          err = json.Unmarshal([]byte(paramValue), &value)
+          if err != nil {
+            http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest)
+            return
+          }
+
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+        }{{if .Required}} else {
+            http.Error(w, "Query argument {{.ParamName}} is required, but not found", http.StatusBadRequest)
+            return
+        }{{end}}
+        {{if .IsStyled}}
+        err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), &params.{{.GoName}})
+        if err != nil {
+          http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest)
+          return
+        }
+        {{end}}
+    {{end}}
+
+      {{if .HeaderParams}}
+        headers := r.Header
+
+        {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
+          if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
+            var {{.GoName}} {{.TypeDef}}
+            n := len(valueList)
+            if n != 1 {
+              http.Error(w, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n), http.StatusBadRequest)
+              return
+            }
+
+          {{if .IsPassThrough}}
+            params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
+          {{end}}
+
+          {{if .IsJson}}
+            err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
+            if err != nil {
+              http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest)
+              return
+            }
+          {{end}}
+
+          {{if .IsStyled}}
+            err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", valueList[0], &{{.GoName}})
+            if err != nil {
+              http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest)
+              return
+            }
+          {{end}}
+
+            params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
+
+          } {{if .Required}}else {
+              http.Error(w, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found", err), http.StatusBadRequest)
+              return
+          }{{end}}
+
+        {{end}}
+      {{end}}
+
+      {{range .CookieParams}}
+        if cookie, err := r.Cookie("{{.ParamName}}"); err == nil {
+
+        {{- if .IsPassThrough}}
+          params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
+        {{end}}
+
+        {{- if .IsJson}}
+          var value {{.TypeDef}}
+          var decoded string
+          decoded, err := url.QueryUnescape(cookie.Value)
+          if err != nil {
+            http.Error(w, "Error unescaping cookie parameter '{{.ParamName}}'", http.StatusBadRequest)
+            return
+          }
+
+          err = json.Unmarshal([]byte(decoded), &value)
+          if err != nil {
+            http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest)
+            return
+          }
+
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+
+        {{- if .IsStyled}}
+          var value {{.TypeDef}}
+          err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value)
+          if err != nil {
+            http.Error(w, "Invalid format for parameter {{.ParamName}}: %s", http.StatusBadRequest)
+            return
+          }
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+
+        }
+
+        {{- if .Required}} else {
+          http.Error(w, "Query argument {{.ParamName}} is required, but not found", http.StatusBadRequest)
+          return
+        }
+        {{- end}}
+      {{end}}
+
+      ctx = context.WithValue(r.Context(), "{{.OperationId}}Params", &params)
+    {{end}}
+    next.ServeHTTP(w, r.WithContext(ctx))
+  })
+}
+{{end}}
+
+
+
diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go
index 82e4715..29d866f 100644
--- a/pkg/codegen/templates/templates.gen.go
+++ b/pkg/codegen/templates/templates.gen.go
@@ -72,6 +72,195 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) {
 	return json.Marshal(object)
 }
 {{end}}
+`,
+	"chi-handler.tmpl": `// Handler creates http.Handler with routing matching OpenAPI spec.
+func Handler(si ServerInterface) http.Handler {
+  r := chi.NewRouter()
+
+{{range .}}r.Group(func(r chi.Router) {
+  r.Use({{.OperationId}}Ctx)
+  r.{{.Method | lower | title }}("{{.Path | swaggerUriToChiUri}}", si.{{.OperationId}})
+})
+{{end}}
+  return r
+}
+`,
+	"chi-interface.tmpl": `type ServerInterface interface {
+{{range .}}// {{.Summary}} ({{.Method}} {{.Path}})
+{{.OperationId}}(w http.ResponseWriter, r *http.Request)
+{{end}}
+}
+`,
+	"chi-middleware.tmpl": `
+{{range .}}{{$opid := .OperationId}}
+
+{{if .RequiresParamObject}}
+// ParamsFor{{.OperationId}} operation parameters from context
+func ParamsFor{{.OperationId}}(ctx context.Context) *{{.OperationId}}Params {
+  return ctx.Value("{{.OperationId}}Params").(*{{.OperationId}}Params)
+}
+{{end}}
+
+// {{$opid}} operation middleware
+func {{$opid}}Ctx(next http.Handler) http.Handler {
+  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+    ctx := r.Context()
+
+    {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
+    var pathErr error
+    var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
+
+    {{if .IsPassThrough}}
+    {{$varName}} = chi.URLParam(r, "{{.ParamName}}")
+    {{end}}
+    {{if .IsJson}}
+    pathErr = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}})
+    if pathErr != nil {
+      http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest)
+      return
+    }
+    {{end}}
+    {{if .IsStyled}}
+    pathErr = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", chi.URLParam(r, "{{.ParamName}}"), &{{$varName}})
+    if pathErr != nil {
+      http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", pathErr), http.StatusBadRequest)
+      return
+    }
+    {{end}}
+
+    ctx = context.WithValue(r.Context(), "{{$varName}}", {{$varName}})
+    {{end}}
+
+    {{if .RequiresParamObject}}
+      var err error
+      // Parameter object where we will unmarshal all parameters from the context
+      var params {{.OperationId}}Params
+
+      {{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
+        if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
+
+        {{if .IsPassThrough}}
+          params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
+        {{end}}
+
+        {{if .IsJson}}
+          var value {{.TypeDef}}
+          err = json.Unmarshal([]byte(paramValue), &value)
+          if err != nil {
+            http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest)
+            return
+          }
+
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+        }{{if .Required}} else {
+            http.Error(w, "Query argument {{.ParamName}} is required, but not found", http.StatusBadRequest)
+            return
+        }{{end}}
+        {{if .IsStyled}}
+        err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), &params.{{.GoName}})
+        if err != nil {
+          http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest)
+          return
+        }
+        {{end}}
+    {{end}}
+
+      {{if .HeaderParams}}
+        headers := r.Header
+
+        {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
+          if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
+            var {{.GoName}} {{.TypeDef}}
+            n := len(valueList)
+            if n != 1 {
+              http.Error(w, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n), http.StatusBadRequest)
+              return
+            }
+
+          {{if .IsPassThrough}}
+            params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
+          {{end}}
+
+          {{if .IsJson}}
+            err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
+            if err != nil {
+              http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest)
+              return
+            }
+          {{end}}
+
+          {{if .IsStyled}}
+            err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", valueList[0], &{{.GoName}})
+            if err != nil {
+              http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest)
+              return
+            }
+          {{end}}
+
+            params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
+
+          } {{if .Required}}else {
+              http.Error(w, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found", err), http.StatusBadRequest)
+              return
+          }{{end}}
+
+        {{end}}
+      {{end}}
+
+      {{range .CookieParams}}
+        if cookie, err := r.Cookie("{{.ParamName}}"); err == nil {
+
+        {{- if .IsPassThrough}}
+          params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
+        {{end}}
+
+        {{- if .IsJson}}
+          var value {{.TypeDef}}
+          var decoded string
+          decoded, err := url.QueryUnescape(cookie.Value)
+          if err != nil {
+            http.Error(w, "Error unescaping cookie parameter '{{.ParamName}}'", http.StatusBadRequest)
+            return
+          }
+
+          err = json.Unmarshal([]byte(decoded), &value)
+          if err != nil {
+            http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest)
+            return
+          }
+
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+
+        {{- if .IsStyled}}
+          var value {{.TypeDef}}
+          err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value)
+          if err != nil {
+            http.Error(w, "Invalid format for parameter {{.ParamName}}: %s", http.StatusBadRequest)
+            return
+          }
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+
+        }
+
+        {{- if .Required}} else {
+          http.Error(w, "Query argument {{.ParamName}} is required, but not found", http.StatusBadRequest)
+          return
+        }
+        {{- end}}
+      {{end}}
+
+      ctx = context.WithValue(r.Context(), "{{.OperationId}}Params", &params)
+    {{end}}
+    next.ServeHTTP(w, r.WithContext(ctx))
+  })
+}
+{{end}}
+
+
+
 `,
 	"client-with-responses.tmpl": `// ClientWithResponses builds on ClientInterface to offer response payloads
 type ClientWithResponses struct {
diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go
index d7215fc..b10bc1b 100644
--- a/pkg/codegen/utils.go
+++ b/pkg/codegen/utils.go
@@ -220,6 +220,21 @@ func SwaggerUriToEchoUri(uri string) string {
 	return pathParamRE.ReplaceAllString(uri, ":$1")
 }
 
+// This function converts a swagger style path URI with parameters to a
+// Chi compatible path URI. We need to replace all of Swagger parameters with
+// "{param}". Valid input parameters are:
+//   {param}
+//   {param*}
+//   {.param}
+//   {.param*}
+//   {;param}
+//   {;param*}
+//   {?param}
+//   {?param*}
+func SwaggerUriToChiUri(uri string) string {
+	return pathParamRE.ReplaceAllString(uri, "{$1}")
+}
+
 // Returns the argument names, in order, in a given URI string, so for
 // /path/{param1}/{.param2*}/{?param3}, it would return param1, param2, param3
 func OrderedParamsFromUri(uri string) []string {


Test patch:
diff --git a/examples/petstore-expanded/chi/petstore_test.go b/examples/petstore-expanded/chi/petstore_test.go
new file mode 100644
index 0000000..f87b716
--- /dev/null
+++ b/examples/petstore-expanded/chi/petstore_test.go
@@ -0,0 +1,156 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/deepmap/oapi-codegen/examples/petstore-expanded/chi/api"
+)
+
+func DoJson(handler http.Handler, method string, url string, body interface{}) *httptest.ResponseRecorder {
+	rr := httptest.NewRecorder()
+	b, _ := json.Marshal(body)
+	req, _ := http.NewRequest(method, url, bytes.NewBuffer(b))
+	handler.ServeHTTP(rr, req)
+
+	return rr
+}
+
+func TestPetStore(t *testing.T) {
+	var err error
+
+	store := api.NewPetStore()
+	h := api.Handler(store)
+
+	t.Run("Add pet", func(t *testing.T) {
+		tag := "TagOfSpot"
+		newPet := api.NewPet{
+			Name: "Spot",
+			Tag:  &tag,
+		}
+
+		rr := DoJson(h, "POST", "/pets", newPet)
+		assert.Equal(t, http.StatusCreated, rr.Code)
+
+		var resultPet api.Pet
+		err = json.NewDecoder(rr.Body).Decode(&resultPet)
+		assert.NoError(t, err, "error unmarshaling response")
+		assert.Equal(t, newPet.Name, resultPet.Name)
+		assert.Equal(t, *newPet.Tag, *resultPet.Tag)
+	})
+
+	t.Run("Find pet by ID", func(t *testing.T) {
+		pet := api.Pet{
+			Id: 100,
+		}
+
+		store.Pets[pet.Id] = pet
+		rr := DoJson(h, "GET", fmt.Sprintf("/pets/%d", pet.Id), nil)
+
+		var resultPet api.Pet
+		err = json.NewDecoder(rr.Body).Decode(&resultPet)
+		assert.NoError(t, err, "error getting pet")
+		assert.Equal(t, pet, resultPet)
+	})
+
+	t.Run("Pet not found", func(t *testing.T) {
+		rr := DoJson(h, "GET", "/pets/27179095781", nil)
+		assert.Equal(t, http.StatusNotFound, rr.Code)
+
+		var petError api.Error
+		err = json.NewDecoder(rr.Body).Decode(&petError)
+		assert.NoError(t, err, "error getting response", err)
+		assert.Equal(t, int32(http.StatusNotFound), petError.Code)
+	})
+
+	t.Run("List all pets", func(t *testing.T) {
+		store.Pets = map[int64]api.Pet{
+			1: api.Pet{},
+			2: api.Pet{},
+		}
+
+		// Now, list all pets, we should have two
+		rr := DoJson(h, "GET", "/pets", nil)
+		assert.Equal(t, http.StatusOK, rr.Code)
+
+		var petList []api.Pet
+		err = json.NewDecoder(rr.Body).Decode(&petList)
+		assert.NoError(t, err, "error getting response", err)
+		assert.Equal(t, 2, len(petList))
+	})
+
+	t.Run("Filter pets by tag", func(t *testing.T) {
+		tag := "TagOfFido"
+
+		store.Pets = map[int64]api.Pet{
+			1: api.Pet{
+				NewPet: api.NewPet{
+					Tag: &tag,
+				},
+			},
+			2: api.Pet{},
+		}
+
+		// Filter pets by tag, we should have 1
+		rr := DoJson(h, "GET", "/pets?tags=TagOfFido", nil)
+		assert.Equal(t, http.StatusOK, rr.Code)
+
+		var petList []api.Pet
+		err = json.NewDecoder(rr.Body).Decode(&petList)
+		assert.NoError(t, err, "error getting response", err)
+		assert.Equal(t, 1, len(petList))
+	})
+
+	t.Run("Filter pets by tag", func(t *testing.T) {
+		store.Pets = map[int64]api.Pet{
+			1: api.Pet{},
+			2: api.Pet{},
+		}
+
+		// Filter pets by non existent tag, we should have 0
+		rr := DoJson(h, "GET", "/pets?tags=NotExists", nil)
+		assert.Equal(t, http.StatusOK, rr.Code)
+
+		var petList []api.Pet
+		err = json.NewDecoder(rr.Body).Decode(&petList)
+		assert.NoError(t, err, "error getting response", err)
+		assert.Equal(t, 0, len(petList))
+	})
+
+	t.Run("Delete pets", func(t *testing.T) {
+		store.Pets = map[int64]api.Pet{
+			1: api.Pet{},
+			2: api.Pet{},
+		}
+
+		// Let's delete non-existent pet
+		rr := DoJson(h, "DELETE", "/pets/7", nil)
+		assert.Equal(t, http.StatusNotFound, rr.Code)
+
+		var petError api.Error
+		err = json.NewDecoder(rr.Body).Decode(&petError)
+		assert.NoError(t, err, "error unmarshaling PetError")
+		assert.Equal(t, int32(http.StatusNotFound), petError.Code)
+
+		// Now, delete both real pets
+		rr = DoJson(h, "DELETE", "/pets/1", nil)
+		assert.Equal(t, http.StatusNoContent, rr.Code)
+
+		rr = DoJson(h, "DELETE", "/pets/2", nil)
+		assert.Equal(t, http.StatusNoContent, rr.Code)
+
+		// Should have no pets left.
+		var petList []api.Pet
+		rr = DoJson(h, "GET", "/pets", nil)
+		assert.Equal(t, http.StatusOK, rr.Code)
+		err = json.NewDecoder(rr.Body).Decode(&petList)
+		assert.NoError(t, err, "error getting response", err)
+		assert.Equal(t, 0, len(petList))
+	})
+}
diff --git a/examples/petstore-expanded/internal/petstore_test.go b/examples/petstore-expanded/echo/petstore_test.go
similarity index 98%
rename from examples/petstore-expanded/internal/petstore_test.go
rename to examples/petstore-expanded/echo/petstore_test.go
index 9b983f9..17e6f3a 100644
--- a/examples/petstore-expanded/internal/petstore_test.go
+++ b/examples/petstore-expanded/echo/petstore_test.go
@@ -11,7 +11,8 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package internal
+
+package main
 
 import (
 	"fmt"
@@ -23,7 +24,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"github.com/deepmap/oapi-codegen/examples/petstore-expanded/api"
+	"github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api"
 	"github.com/deepmap/oapi-codegen/pkg/middleware"
 	"github.com/deepmap/oapi-codegen/pkg/testutil"
 )
@@ -34,7 +35,7 @@ func TestPetStore(t *testing.T) {
 	e := echo.New()
 
 	// Now, we create our empty pet store
-	store := NewPetStore()
+	store := api.NewPetStore()
 
 	// Get the swagger description of our API
 	swagger, err := api.GetSwagger()
diff --git a/internal/test/issues/issue-52/issue_test.go b/internal/test/issues/issue-52/issue_test.go
index cc5222f..b6b1555 100644
--- a/internal/test/issues/issue-52/issue_test.go
+++ b/internal/test/issues/issue-52/issue_test.go
@@ -54,10 +54,10 @@ func TestIssue(t *testing.T) {
 	require.NoError(t, err)
 
 	opts := codegen.Options{
-		GenerateClient: true,
-		GenerateServer:true,
-		GenerateTypes:true,
-		EmbedSpec:true,
+		GenerateClient:     true,
+		GenerateEchoServer: true,
+		GenerateTypes:      true,
+		EmbedSpec:          true,
 	}
 
 	_, err = codegen.Generate(swagger, "issue_52", opts)
diff --git a/pkg/codegen/codegen_test.go b/pkg/codegen/codegen_test.go
index 2709c44..d39384f 100644
--- a/pkg/codegen/codegen_test.go
+++ b/pkg/codegen/codegen_test.go
@@ -7,7 +7,8 @@ import (
 	"net/http"
 	"testing"
 
-	examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/api"
+	examplePetstoreClient "github.com/deepmap/oapi-codegen/examples/petstore-expanded"
+	examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api"
 
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/golangci/lint-1"
@@ -19,10 +20,10 @@ func TestExamplePetStoreCodeGeneration(t *testing.T) {
 	// Input vars for code generation:
 	packageName := "api"
 	opts := Options{
-		GenerateClient: true,
-		GenerateServer: true,
-		GenerateTypes:  true,
-		EmbedSpec:      true,
+		GenerateClient:     true,
+		GenerateEchoServer: true,
+		GenerateTypes:      true,
+		EmbedSpec:          true,
 	}
 
 	// Get a spec from the example PetStore definition:
@@ -62,7 +63,7 @@ func TestExamplePetStoreParseFunction(t *testing.T) {
 	}
 	cannedResponse.Header.Add("Content-type", "application/json")
 
-	findPetByIDResponse, err := examplePetstore.ParsefindPetByIdResponse(cannedResponse)
+	findPetByIDResponse, err := examplePetstoreClient.ParsefindPetByIdResponse(cannedResponse)
 	assert.NoError(t, err)
 	assert.NotNil(t, findPetByIDResponse.JSON200)
 	assert.Equal(t, int64(5), findPetByIDResponse.JSON200.Id)
@@ -76,10 +77,10 @@ func TestExampleOpenAPICodeGeneration(t *testing.T) {
 	// Input vars for code generation:
 	packageName := "testswagger"
 	opts := Options{
-		GenerateClient: true,
-		GenerateServer: true,
-		GenerateTypes:  true,
-		EmbedSpec:      true,
+		GenerateClient:     true,
+		GenerateEchoServer: true,
+		GenerateTypes:      true,
+		EmbedSpec:          true,
 	}
 
 	// Get a spec from the test definition in this file:
@@ -196,7 +197,7 @@ paths:
 
 components:
   schemas:
-  
+
     Test:
       properties:
         name: