Routes and scopes

Routing in celerity is done using Routes and Scopes. Routes represent an endpoint in the application that can be handled. Scopes are groups of Routes that can share middleware and URL paths.

Route handlers

When creating routes you must specify a route handler which is a function that is invoked when the route is requested. The handler has the following definition:

func MyHandler (c celerity.Context) celerity.Response 

The provided context object contains information about the request, as well as helper functions for generating response objects.

Handler responses

Celerity is built with JSON APIs in mind. You can return any value using the context’s Response function (which is aliased to R). The value passed will automatically be serialized into JSON and used as the response.

A simple response

func MyHandler(c celerity.Context) celerity.Response {
    users := GetUsers()
    c.R(users)
}

Transforming responses

Sometimes it might be necessary to transform, or clean, data before responding. In this case you can create an anonymous struct and respond with it.

func MyHandler(c celerity.Context) celerity.Response {
  user := GetUser()
  profile := GetProfile()
  
  resp := struct {
      User: User `json:"users"`
      Profile Profile `json:"profile"`
  }{
      Users: users,
      Profile: profile,
  }
  c.R(resp)
}

Error responses

If an error occurs in the endpoint that can not be recovered from. The Fail() function will respond with an error status and message. Fail() acts differently depending on the environment. When running in a development environment the errors message will be relayed to the client. In a production environment a generic error message is displayed.

NOTE: The HTTP status code is always set to 500.

func MyHandler(c celerity.Context) celerity.Response {
    res, err := doTheThing()
    if error != nil {
        return c.Fail(err)
    }
    return c.R(res)
}

To respond with an intentional error, such as invalid data or authentication related errors. You will want to display an error message, even in a production environment, and set the HTTP status code. The celerity.NewErrorResponse will create a response object that can be returned.

func MyHandler(c celerity.Context) celerity.Response {
    res, err := doTheThing()
    if error != nil {
        return celerity.NewErrorResponse(http.StatusBadRequest, err.Error())
    }
    return c.R(res)
}

Creating routes

To create a route use the Route function or one of the HTTP method functions.

svr := celerity.New()
svr.GET("/users", func (c celerity.Context) celerity.Response {
    users := GetUsers()
    c.R(users)
})

Working with scopes

Scopes can logically group routes together in order to share preprocessing logic (middleware) and to help organize paths. Authenticated and unauthenticated routes for example can be grouped into selenate scopes. One scope can be given an authentication middleware and routes in the other scope will not use it.

Routes are defined just like they are on the base server. In fact, the Routing functions on the base server simply alias to an internal root scope that is generated with the server’s router.

Creating scopes

svr := celerity.New()
// unauthenticated routes
svr.Route("/login")

//authenticated routes
secured := svr.Scope("/")
secured.GET("/profile", profileHandler)

Nesting scopes

Scopes can be nested at any level. This allows freedom to create more complicated routing rules.

svr := celerity.New()
// unauthenticated routes
svr.Route("/login")

//authenticated routes
secured := svr.Scope("/")
secured.GET("/profile", profileHandler)

// administrative routes
admin := secured.Scope("/admin")
admin.GET("/stats", statsHandler)

Scope collision

Scopes can collide with other scopes safely, as long as the routes inside them don’t collide. This means you can have separate scopes that have very similar paths.

In the example above both the default root scope’s path is “/” as well as the secured scope. This means that any path could potentially be placed in either scope. The admin scope however will only match routes that are prefixed with /admin

Using middleware

Middleware allows a scope to process logic and transform the request before the route is invoked. This is useful for any type of action that is shared between routes: Such as validating authentication, adding headers, etc.

Middleware generally has a factory function which generates the middleware handler for you and can be passed to the server or a scope, with the Use function.

import(
  "github.com/5Sigma/celerity"
  "github.com/5Sigma/celerity/middleware"
)

svr := celerity.New()
svr.Use(middleware.CORSMiddleware())

Celerity comes with a number of built in middlewares that can be easily added to the server. Alternatively you can easily create your own middleware.

last modified Thursday, June 21, 2018
Previous in Guides
Testing Routes
Unit and integration tests for routes and route handlers.
Next in Guides
Extracting Request Body Data
Extracting data out of the body of a request
Celerity is maintained by 5Sigma. Source code is available at Github.