{"id":18039558,"url":"https://github.com/adamluzsi/gorest","last_synced_at":"2025-04-05T01:13:49.376Z","repository":{"id":57518623,"uuid":"242185202","full_name":"adamluzsi/gorest","owner":"adamluzsi","description":"minimalist framework for building restful APIs","archived":false,"fork":false,"pushed_at":"2020-04-16T15:11:37.000Z","size":111,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-10T09:29:26.153Z","etag":null,"topics":["convention","framework","golang","gorest","rest","rest-api","restful","restful-api"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adamluzsi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-02-21T16:39:13.000Z","updated_at":"2021-04-16T04:59:40.000Z","dependencies_parsed_at":"2022-09-26T18:01:44.960Z","dependency_job_id":null,"html_url":"https://github.com/adamluzsi/gorest","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamluzsi%2Fgorest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamluzsi%2Fgorest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamluzsi%2Fgorest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamluzsi%2Fgorest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adamluzsi","download_url":"https://codeload.github.com/adamluzsi/gorest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247271527,"owners_count":20911587,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["convention","framework","golang","gorest","rest","rest-api","restful","restful-api"],"created_at":"2024-10-30T14:09:45.177Z","updated_at":"2025-04-05T01:13:49.358Z","avatar_url":"https://github.com/adamluzsi.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/adamluzsi/gorest.svg?branch=master)](https://travis-ci.org/adamluzsi/gorest)\n[![GoDoc](https://godoc.org/github.com/adamluzsi/gorest?status.png)](https://godoc.org/github.com/adamluzsi/gorest)\n[![Go Report Card](https://goreportcard.com/badge/github.com/adamluzsi/gorest)](https://goreportcard.com/report/github.com/adamluzsi/gorest)\n[![codecov](https://codecov.io/gh/adamluzsi/gorest/branch/master/graph/badge.svg)](https://codecov.io/gh/adamluzsi/gorest)\n# gorest\n\n`gorest` is a minimalist approach to build restful API designs through composition.\n\n## What problem it solves?\n\nThe reason I made this package, because when I design restful APIs,\nI prefer to decouple the resource operations from the resource retrieve aspect.\nThe reason for this is that when I need to represent relationship of resources,\nI often found myself testing the same logic over and over again in each `http.Handler`\nabout which input value is used used to retrieve a certain resource.\nThis then caused leaky abstraction between the `http.Handler` implementations,\nsince the handler had to know about how a resource is specified on the API,\nand also how to retrieve this resource.\n\nA simple example to this is, imagine you have `users`, `organizations` and `permissions` collections.\nGiven your API provides `/users/...` path where the API user can retrieve information about other users.\nAlso the API requester may retrieve information about a certain user publicly listed organizations under `/users/{user-id}/organizations`.\n\nNow if you build your handler in a way that your `organizations` collection handler is aware that user-id must be a path parameter,\nand must be used to retrieve the user entity, you couple the two heavily together.\nYou also bind the knowledge that the API requester must be checked if the API requester has permission to know this information.\n\nNow imagine if your API need a `/organizations` collection that represent the API requester's organization.\nMaybe a `/organizations/{organization-id}/permissions` as well.\nNow you need to somehow inject the user-id in a way that it will be compatible with your `organizations` collection handler implementation.\n\nI prefer the pipeline pattern to create steps where I represent such aspects as who is the user in the current context.\nThis allow me to reduce the need to test this responsibility in all the collection handler that depends on a resource existence.\nIn the case of the `organizations` handler, this would be the dependency on a `current context's user`. \n\nThis convention led me to the pattern where I have a `http.Handler` that act as a controller,\na `http.ServeMux` that composite the controllers, and a couple of middleware.\nUsing a middleware allowed me to do permission validations \nand resource retrieval based on a defined input parameter.\nThen this resource can be stored in the context,\nand in the collection handler tests, I can define these resources as dependencies \nthat must be present in the request context in order to use the collection handler.\n\nThis pattern worked nicely so far, as it allows guard clauses for handling cases\nwhen a resource is not found or should not be returned to the requester.\nAs a side effect, it caused a lot of boilerplate while I used `http.ServeMux` purely. \n\nUsing a `router` that would allow me to have the path params would be less efficient,\nas my controllers under a certain resource use the assumption that the resource exists and can be used already,\nto remove a lot of repetition from each controller code.\n\n## How does this package solve the problem?\n\nThe above-mentioned problem is solved by introducing a convention.\nBy having a well-tested controller package, that can have actions such as `List`, `Create`, `Show` and so on\nit becomes easier to focus on the operation aspects and the retrieval aspects.\n\nThe resource retrieval and visibility validation is solved by having a controller function ([ContextWithResource](https://godoc.org/github.com/adamluzsi/gorest#ContextHandler)),\nthat focus only on this, and the rest of the resource oriented actions like `Show`, `Update` and `Delete`\nno longer have to cover in they specification this.\n\nMore about this between the examples.\n\n## Resource Oriented Design\n\nThe architectural style of REST was introduced primarily to work well with HTTP/1.1.\nIt also helps to reduce the learning curve a developer need to do in order to understand how to use a new API.\nIts core principle is to define named resources that can be manipulated using a small number of methods.\nThe resources and methods are known as nouns and verbs of APIs.\nWith the HTTP protocol, the resource names naturally map to URLs,\nand methods naturally map to HTTP methods POST, GET, PUT, PATCH, and DELETE.\nThis results in much fewer things to learn, since developers can focus on the resources and their relationship,\nand assume that they have the same small number of standard methods.\n\n## What is a REST API?\n\nA REST API is modeled as collections of individually-addressable resources (the nouns of the API).\nResources are referenced with their resource names and manipulated via a small set of methods (also known as verbs or operations).\n\nStandard methods for REST APIs (also known as REST methods) are List, Show, Create, Update, and Delete.\n\nYou can create a controller simply as:\n\n```go\npackage myhttpapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"github.com/adamluzsi/gorest\"\n)\n\nfunc NewMyCollectionHandler() http.Handler {\n    return gorest.NewHandler(MyCollectionController{})\n}\n\ntype MyCollectionController struct{}\n\nfunc (ctrl MyCollectionController) List(w http.ResponseWriter, r *http.Request) {}\n```\n\nCustom methods (also known as custom verbs or custom operations) are also available to API designers\nfor functionality that doesn't easily map to one of the standard methods,\nsuch as database transactions.\n\nYou can apply them using the multiplexer interface of the `gorest.Handler`\n\n```go\nhandler := gorest.NewHandler(TestController{})\nvar myCustomOperationHandler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})\nhandler.Handle(`/my-custom-operation`, myCustomOperationHandler)\n```\n\nNote: Custom verbs does not mean creating custom HTTP verbs to support custom methods.\nFor HTTP-based APIs, they simply map to the most suitable HTTP verbs.\n\n## Design flow\n\nwe suggests taking the following steps when designing resource-oriented APIs:\n- Determine what types of resources an API provides.\n- Determine the relationships between resources.\n- Decide the resource name schemes based on types and relationships.\n- Decide the resource schemas.\n- Attach minimum set of methods to resources.\n\n### Resources\n\nA resource-oriented API is generally modeled as a resource hierarchy, \nwhere each node is either a simple resource or a collection resource.\nFor convenience, they are often called as a resource and a collection, respectively.\nA `gorest.Controller` represents operations on a collection resources.\nNot all function must be implemented, if the given collection doesn't need it.   \n\nA collection contains a list of resources of the same type. \nFor example, a user has a collection of contacts.\nA resource has some state and zero or more sub-resources. \nEach sub-resource can be either a simple resource or a collection resource.\nFor example, an API may have a collection of users, each user has a collection of messages, a profile resource, and several setting resources.\n\nWhile there is some conceptual alignment between storage systems and REST APIs, \na service with a resource-oriented API is not necessarily a database,\nand has enormous flexibility in how it interprets resources and methods.\nFor example, creating a calendar event (resource) may create additional events for attendees,\nsend email invitations to attendees, reserve conference rooms, and update video conference schedules.\n\n### Methods\n\nThe key characteristic of a resource-oriented API is that it emphasizes resources (data model) over the methods performed on the resources (functionality).\nA typical resource-oriented API exposes a large number of resources with a small number of methods.\nThe methods can be either the standard methods or custom methods. \nFor `gorest`, the standard methods are: List, Show, Create, Update, and Delete.\n\nWhere API functionality naturally maps to one of the standard methods, that method should be used in the API design.\nFor functionality that does not naturally map to one of the standard methods, custom methods may be used.\nCustom methods offer the same design freedom as traditional RPC APIs, \nwhich can be used to implement common programming patterns, such as database transactions or data analysis.\n\n## Examples\n\n[You can find examples regarding the usage of the package between the godoc examples.](https://godoc.org/github.com/adamluzsi/gorest#pkg-examples)\n\nThe following sections present a few examples on how to use a `gorest` controller.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/adamluzsi/gorest\"\n)\n\nfunc main() {\n    mux := http.NewServeMux()\n    gorest.Mount(mux, `/my-collection-id-name-in-plural/`, gorest.NewHandler(MyCollectionController{}))\n\n    if err := http.ListenAndServe(`:8080`, mux); err != nil {\n\t\tpanic(err.Error())\n\t}\n}\n\ntype MyCollectionController struct{}\n\nfunc (ctrl MyCollectionController) List(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `list`)\n}\n\nfunc (ctrl MyCollectionController) ContextWithResource(ctx context.Context, resourceID string) (newContext context.Context, found bool, err error) {\n\treturn context.WithValue(ctx, `id`, resourceID), true, nil\n}\n\nfunc (ctrl MyCollectionController) Show(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `show:%s`, r.Context().Value(`id`))\n}\n```\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/adamluzsi/gorest\"\n)\n\ntype MyCollectionController struct{}\n\nfunc (ctrl MyCollectionController) ContextWithResource(ctx context.Context, resourceID string) (newContext context.Context, found bool, err error) {\n\treturn context.WithValue(ctx, `id`, resourceID), true, nil\n}\n\nfunc (ctrl MyCollectionController) Create(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `create`)\n}\n\nfunc (ctrl MyCollectionController) List(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `list`)\n}\n\nfunc (ctrl MyCollectionController) Show(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `show:%s`, r.Context().Value(`id`))\n}\n\nfunc (ctrl MyCollectionController) Update(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `update:%s`, r.Context().Value(`id`))\n}\n\nfunc (ctrl MyCollectionController) Delete(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `delete:%s`, r.Context().Value(`id`))\n}\n\nfunc (ctrl MyCollectionController) NotFound(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `not-found`)\n}\n\nfunc (ctrl MyCollectionController) InternalServerError(w http.ResponseWriter, r *http.Request) {\n\t_, _ = fmt.Fprintf(w, `internal-server-error`)\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadamluzsi%2Fgorest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadamluzsi%2Fgorest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadamluzsi%2Fgorest/lists"}