restapi

package
v0.166.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2023 License: Apache-2.0 Imports: 14 Imported by: 0

README

package restapi

REST API stands for Representational State Transfer and is an architectural pattern for creating web services.

Roy Fielding developed it in 2000 and has led to a growing collection of RESTful web services that follow the REST principles. Now, REST APIs see widespread use by application developers due to how simply it communicates with other machines over complex operations like COBRA, RPC, or Simple Object Access Protocol (SOAP).

REST is a ruleset that defines best practices for sharing data between clients and the server. It’s essentially a design style used when creating HTTP or other APIs that only asks you to use CRUD functions, regardless of the complexity.

REST applications use HTTP methods like GET, POST, DELETE, and PUT. REST emphasizes the scalability of components and the simplicity of interfaces.

While neglecting a portion of your tools may seem counterintuitive, it ultimately forces you to describe complex behaviours in simple terms.

Not all HTTP APIs are REST APIs.

The API needs to meet the following architectural requirements listed below to be considered a REST API.

These constraints combine to create an application with strong boundaries and a clear separation of concerns. The client receives server data when requested. The client manipulates or displays the data. The client notifies the server of any state changes. REST APIs don’t conceal data from the client, only implementations.

Client-server

REST applications have a server that manages application data and state. The server communicates with a client that handles the user interactions. A clear separation of concerns divides the two components.

Stateless

Servers don’t maintain client state; clients manage their application state. The client’s requests to the server contain all the information required to process them.

Cacheable

Being cacheable is one of the architectural constraints of REST. The servers must mark their responses as cacheable or not. Systems and clients can cache responses when convenient to improve performance. They also dispose of non-cacheable information, so no client uses stale data.

per HTTP methods

GET requests should be cachable by default – until an exceptional condition arises. Usually, browsers treat all GET requests as cacheable.

POST requests are not cacheable by default but can be made cacheable if either an Expires header or a Cache-Control header with a directive, to explicitly allows caching to be added to the response.

Responses to PUT and DELETE requests are not cacheable. Please note that HTTP dates are always expressed in GMT, never local time.

Cache-Control Headers

Below given are the main HTTP response headers that we can use to control caching behaviour:

Expires Header

The Expires HTTP header specifies an absolute expiry time for a cached representation. Beyond that time, a cached representation is considered stale and must be re-validated with the origin server. The server can include time up to one year in the future to indicate a never expiring cached representation.

Expires: Fri, 20 May 2016 19:20:49 GMT
Cache-Control Header

The header value comprises one or more comma-separated directives. These directives determine whether a response is cacheable and, if so, by whom and for how long, e.g. max-age directives.

Cache-Control: max-age=3600

Cacheable responses (whether to a GET or a POST request) should also include a validator — either an ETag or a Last-Modified header.

ETag Header

An ETag value is an opaque string token that a server associates with a resource to identify the state of the resource over its lifetime uniquely. If the resource at a given URL changes, the server must generate a new Etag value. A comparison of them can determine whether two representations of a resource are the same.

While requesting a resource, the client sends the ETag in the If-None-Match header field to the server. The server matches the Etag of the requested resource and the value sent in the If-None-Match header. If both values match, the server sends back a 304 Not Modified status without a body, which tells the client that the cached response version is still good to use (fresh).

ETag: "abcd1234567n34jv"
Last-Modified Header

Whereas a response’s Date header indicates when the server generated the response, the Last-Modified header indicates when the server last changed the associated resource.

This header is a validator to determine if the resource is the same as the previously stored one by the client’s cache. Less accurate than an ETag header, it is a fallback mechanism.

The Last-Modified value cannot be greater than the Date value. Note that the Date header is listed in the forbidden header names.

Uniform interface

Uniform interface is REST’s most well-known feature or rule. Fielding says:

The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components.

REST services provide data as resources with a consistent namespace.

Layered system

Components in the system cannot see beyond their layer. This confined scope allows you to add load-balancers easily and proxies to improve authentication security or performance.

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultBodyReadLimit int64 = 256 * 1024 * 1024
View Source
var ErrEntityAlreadyExist = errorkit.UserError{
	ID:      "entity-already-exists",
	Message: "The entity could not be created as it already exists.",
}
View Source
var ErrEntityNotFound = errorkit.UserError{
	ID:      "entity-not-found",
	Message: "The requested entity is not found in this resource.",
}
View Source
var ErrInternalServerError = errorkit.UserError{
	ID:      "internal-server-error",
	Message: "An unexpected internal server error occurred.",
}
View Source
var ErrInvalidRequestBody = errorkit.UserError{
	ID:      "invalid-create-request-body",
	Message: "The request body is invalid.",
}
View Source
var ErrMalformedID = errorkit.UserError{
	ID:      "malformed-id-in-path",
	Message: "The received entity id in the path is malformed.",
}
View Source
var ErrMethodNotAllowed = errorkit.UserError{
	ID:      "restapi-method-not-allowed",
	Message: "The requested RESTful method is not supported.",
}
View Source
var ErrPathNotFound = errorkit.UserError{
	ID:      "path-not-found",
	Message: "The requested path is not found.",
}
View Source
var ErrRequestEntityTooLarge = errorkit.UserError{
	ID:      "request-entity-too-large",
	Message: "The request body was larger than the size limit allowed for the server.",
}

Functions

func ErrorMapping

func ErrorMapping(ctx context.Context, err error, dto *rfc7807.DTO)

func Mount

func Mount(multiplexer multiplexer, pattern string, handler http.Handler)

Mount will help to register a handler on a request multiplexer in both as the concrete path to the handler and as a prefix match. example:

if pattern -> "/something"
registered as "/something" for exact match
registered as "/something/" for prefix match

func MountPoint

func MountPoint(mountPoint Path, next http.Handler) http.Handler

Types

type BeforeHook added in v0.124.0

type BeforeHook http.HandlerFunc

type CreateOperation added in v0.124.0

type CreateOperation[Entity, ID, DTO any] struct {
	BeforeHook BeforeHook
}

type DeleteOperation added in v0.124.0

type DeleteOperation[Entity, ID, DTO any] struct {
	BeforeHook BeforeHook
}

type ErrorHandler

type ErrorHandler interface {
	HandleError(w http.ResponseWriter, r *http.Request, err error)
}

type Handler

type Handler[Entity, ID, DTO any] struct {
	// Resource is the CRUD Resource object that we wish to expose as a restful API resource.
	Resource crud.ByIDFinder[Entity, ID]
	// Mapping takes care mapping back and forth Entity into a DTO, and ID into a string.
	// ID needs mapping into a string because it is used as part of the restful paths.
	Mapping Mapping[Entity, ID, DTO]
	// ErrorHandler is used to handle errors from the request, by mapping the error value into an error DTO.
	ErrorHandler ErrorHandler
	// Router is the sub-router, where you can define routes related to entity related paths
	//  > .../:id/sub-routes
	Router *Router
	// BodyReadLimit is the max bytes that the handler is willing to read from the request body.
	BodyReadLimit int64
	Operations[Entity, ID, DTO]
}

Handler is a HTTP Handler that allows you to expose a resource such as a repository as a Restful API resource. Depending on what CRUD operation is supported by the Handler.Resource, the Handler support the following actions:

  • Index: GET /
  • Show: GET /:id
  • Create: POST /
  • Update: PUT /:id
  • Delete: Delete /:id
Example
m := memory.NewMemory()
fooRepository := memory.NewRepository[Foo, int](m)

h := restapi.Handler[Foo, int, FooDTO]{
	Resource: fooRepository,
	Mapping:  FooMapping{},
}

if err := http.ListenAndServe(":8080", h); err != nil {
	log.Fatalln(err.Error())
}
Output:

func (Handler[Entity, ID, DTO]) ServeHTTP

func (h Handler[Entity, ID, DTO]) ServeHTTP(w http.ResponseWriter, r *http.Request)

type IndexOperation added in v0.124.0

type IndexOperation[Entity, ID, DTO any] struct {
	BeforeHook BeforeHook
	Override   func(r *http.Request) iterators.Iterator[Entity]
}

type Mapping

type Mapping[Entity, ID, DTO any] interface {
	LookupID(Entity) (ID, bool)
	SetID(*Entity, ID)

	EncodeID(ID) (string, error)
	ParseID(string) (ID, error)

	ContextWithID(context.Context, ID) context.Context
	ContextLookupID(ctx context.Context) (ID, bool)

	MapEntity(context.Context, DTO) (Entity, error)
	MapDTO(context.Context, Entity) (DTO, error)
}

type Operations added in v0.124.0

type Operations[Entity, ID, DTO any] struct {
	// Index is an OPTIONAL field if you wish to customise the index operation's behaviour
	//   GET /
	//
	Index IndexOperation[Entity, ID, DTO]
	// Create is an OPTIONAL field if you wish to customise the create operation's behaviour
	//   POST /
	//
	Create CreateOperation[Entity, ID, DTO]
	// Show is an OPTIONAL field if you wish to customise the show operation's behaviour
	//   GET /:id
	//
	Show ShowOperation[Entity, ID, DTO]
	// Update is an OPTIONAL field if you wish to customise the update operation's behaviour
	//   PUT /:id
	//   PATCH /:id
	//
	Update UpdateOperation[Entity, ID, DTO]
	// Delete is an OPTIONAL field if you wish to customise the delete operation's behaviour
	//   DELETE /:id
	//
	Delete DeleteOperation[Entity, ID, DTO]
}

Operations is an optional config where you can customise individual restful operations.

type Path

type Path = string

type Router

type Router struct {
	// contains filtered or unexported fields
}

func NewRouter

func NewRouter(configure ...func(*Router)) *Router

func (*Router) Mount

func (router *Router) Mount(path Path, handler http.Handler)

func (*Router) MountRoutes

func (router *Router) MountRoutes(routes Routes)

func (*Router) ServeHTTP

func (router *Router) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request)

type Routes

type Routes map[Path]http.Handler
Example
m := memory.NewMemory()
fooRepository := memory.NewRepository[Foo, int](m)
barRepository := memory.NewRepository[Bar, string](m)

r := restapi.NewRouter(func(router *restapi.Router) {
	router.MountRoutes(restapi.Routes{
		"/v1/api/foos": restapi.Handler[Foo, int, FooDTO]{
			Resource: fooRepository,
			Mapping:  FooMapping{},
			Router: restapi.NewRouter(func(router *restapi.Router) {
				router.MountRoutes(restapi.Routes{
					"/bars": restapi.Handler[Bar, string, BarDTO]{
						Resource: barRepository,
						Mapping:  BarMapping{},
					}})
			}),
		},
	})
})

// Generated endpoints:
//
// Foo Index  - GET       /v1/api/foos
// Foo Create - POST      /v1/api/foos
// Foo Show   - GET       /v1/api/foos/:foo_id
// Foo Update - PATCH/PUT /v1/api/foos/:foo_id
// Foo Delete - DELETE    /v1/api/foos/:foo_id
//
// Bar Index  - GET       /v1/api/foos/:foo_id/bars
// Bar Create - POST      /v1/api/foos/:foo_id/bars
// Bar Show   - GET       /v1/api/foos/:foo_id/bars/:bar_id
// Bar Update - PATCH/PUT /v1/api/foos/:foo_id/bars/:bar_id
// Bar Delete - DELETE    /v1/api/foos/:foo_id/bars/:bar_id
//
if err := http.ListenAndServe(":8080", r); err != nil {
	log.Fatalln(err.Error())
}
Output:

type ShowOperation added in v0.124.0

type ShowOperation[Entity, ID, DTO any] struct {
	BeforeHook BeforeHook
}

type UpdateOperation added in v0.124.0

type UpdateOperation[Entity, ID, DTO any] struct {
	BeforeHook BeforeHook
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL