tinka alpha

HTTP context

This guide covers the HTTP context in Tinka. You will learn how to:

Overview

Every request gets one HTTP context, a *tinka.Ctx, which the App passes to each middleware and to the handler. It holds everything about the request in one place: the incoming *http.Request, the response being built, the request's ID, and the request's dependencies. Handlers take it as their only argument, so their signatures stay the same however much of the request they use:

posts.go
package main

import (
	"github.com/mucolabs/tinka"
)

type Post struct {
	ID    int64  `json:"id"`
	Title string `json:"title"`
}

func showPost(c *tinka.Ctx) (Post, error) {
	id, err := c.ParamInt("id")
	if err != nil {
		return Post{}, err
	}
	return Post{ID: id, Title: "Hello, Tinka"}, nil
}

A Ctx has methods for the request, such as Param, Query, IP and Accepts, and for the response, such as Status and Header. Those guides cover them.

Pass it as a context

A Ctx is a context.Context: its deadline, cancellation and values are the request's. Pass it to the database, to HTTP clients and to your own code, and their work stops when the client disconnects or the request times out:

posts.go
func (s *PostService) Find(ctx context.Context, id int64) (Post, error) {
	var p Post
	err := s.db.QueryRowContext(ctx, "SELECT id, title FROM posts WHERE id = $1", id).Scan(&p.ID, &p.Title)
	return p, err
}

func (h Posts) Show(c *tinka.Ctx) (Post, error) {
	id, err := c.ParamInt("id")
	if err != nil {
		return Post{}, err
	}
	return h.Service.Find(c, id)
}

Records you log with it carry the request's ID as request_id, as the Logger guide shows. c.ID() returns the same ID, which the response also carries in its X-Request-Id header.

Reach the request and the response writer

c.Request() returns the *http.Request, for anything the Ctx has no method for, such as request headers:

go
	agent := c.Request().Header.Get("User-Agent")

c.ResponseWriter() returns the http.ResponseWriter, for a handler that writes the response itself, such as one that streams Server-Sent Events. Once anything is written, the handler's returned value is ignored. When a middleware replaces the request or the writer, as tinka.Timeout and compression middleware do, both methods return the replacement.

Resolve request-scoped dependencies

Give the App your dependency injection container, and c.Make resolves from it through a resolver made for this request. A Scoped binding is built once per request, and can ask for the *tinka.Ctx itself:

main.go
	container := di.New()
	container.Scoped(func(r *di.Resolver) (*Cart, error) {
		c := r.MustMake[*tinka.Ctx]()
		return carts.ForSession(c, c.Query("session"))
	})

	app := tinka.New(tinka.HTTPConfig{Container: container})
	app.Get("/cart", func(c *tinka.Ctx) (*Cart, error) {
		return c.Make[*Cart]()
	})

Resolve singletons at boot instead, and capture them in your handlers: a request that never calls Make pays nothing for the container.

See also: Request, Response, Middleware, Dependency injection