# Application lifecycle

> How an App boots, starts, serves and shuts down gracefully, and where your setup and cleanup code goes.

This guide covers the lifecycle of a Tinka App. You will learn:

- The phases an App goes through: boot, start, ready and shutdown
- How to run an App as a web server with `Serve`
- Where to register routes and run setup code
- How an App shuts down gracefully, and how long it waits
- What happens when the App fails to start

## Overview

An App moves through four phases. It **boots**: providers register their services and configure them. It **starts**: providers and your start hooks assemble the App, registering routes. It becomes **ready**: it serves requests, and long-running work begins. Finally it **shuts down**: it stops taking requests, finishes the ones in flight, and releases every resource it opened.

The order is fixed, so every piece of setup and cleanup code has one right place, and runs after what it depends on. [Service providers](service-providers.md) hook into each phase for the services they bring, and the App has hooks of its own for application code.

## Serve the App

`Serve` runs the whole lifecycle for a web server: it boots and starts the App, listens on its address, runs the ready hooks, and serves requests until the process receives `SIGINT` (Ctrl+C) or `SIGTERM` (a deployment stopping it). Then it shuts the App down gracefully and returns.

```go title="main.go"
package main

import (
	"context"
	"log/slog"
	"os"

	"github.com/mucolabs/tinka"
)

func main() {
	app := tinka.New(tinka.HTTPConfig{Addr: ":3000"})
	app.Provide(&DatabaseProvider{URL: os.Getenv("DATABASE_URL")})
	app.OnStart(func(ctx context.Context) error {
		return routes(app)
	})

	if err := app.Serve(context.Background()); err != nil {
		slog.Error("server failed", "err", err)
		os.Exit(1)
	}
}
```

`Serve` returns `nil` after a clean shutdown. Any error, from starting, from serving or from shutting down, is returned for `main` to report, and a non-zero exit code tells your platform the App failed.

## The boot phase

Each provider's `Register` hook binds its services in the [container](dependency-injection.md), in the order the providers were added. Then each provider's `Boot` hook configures services, now that every provider registered, and the App's boot hooks run. Last, the container is validated: a service that depends on something nobody registered stops the App here, at boot, instead of on the first request that needs it.

## The start phase

Each provider's `Start` hook runs, then the App's start hooks. This is where the App is assembled, and where your routes belong: a start hook runs after every provider set up what routes use, such as a database pool or a template renderer.

```go title="routes.go"
func routes(app *tinka.App) error {
	posts, err := app.Container().Make[*PostService]()
	if err != nil {
		return err
	}
	app.Get("/posts", posts.List)
	app.Get("/posts/{id}", posts.Show).As("posts.show")
	return nil
}
```

Routes that need no services can be registered in `main` directly, before `Serve`.

## The ready phase

Once the App listens, each provider's `Ready` hook runs, then the App's ready hooks, and the App logs `server started` with its address. Use ready hooks for long-running work that should begin only when the App serves: background workers, scheduled jobs, or a message that the deployment is live.

The context a ready hook receives ends when shutdown begins, so work started with it stops in time:

```go title="main.go"
	app.OnReady(func(ctx context.Context) error {
		go reports.RunNightly(ctx) // returns once ctx ends
		return nil
	})
```

Requests can arrive while ready hooks run: the server accepts them from the moment it listens.

## The shutdown phase

When `Serve` receives a signal, the App shuts down in this order:

1. [Readiness](../digging-deeper/health-checks.md) starts failing, and the App keeps serving for `DrainDelay`, if set, so load balancers stop sending requests.
2. The server stops accepting connections and waits for requests in flight to finish.
3. The App's shutdown hooks run, the last added first.
4. Each provider's `Shutdown` hook runs, in the reverse order the providers were added, so a provider shuts down before those it depends on.
5. The container shuts down, closing every service it built that has a `Close` or `Shutdown` method.

Every step runs even when one fails, so one service failing to close never leaks the others, and `Serve` returns every error together. A second signal during shutdown ends the process at once.

```go title="main.go"
	app.OnShutdown(func(ctx context.Context) error {
		return queue.Flush(ctx)
	})
```

The whole shutdown has a deadline, `ShutdownTimeout`, 10 seconds by default. Requests still running when it passes are cut off, and their connections closed. Keep the timeout below your platform's own deadline, such as the 30-second grace period Kubernetes gives a pod, so the App finishes its cleanup before the platform kills it.

```go title="main.go"
	app := tinka.New(tinka.HTTPConfig{
		Addr:            ":3000",
		ShutdownTimeout: 20 * time.Second,
	})
```

:::warning
Shutdown hooks receive a context that ends at the deadline. A hook that ignores it, such as one waiting on a channel that never closes, holds the whole shutdown until the platform kills the process, skipping every cleanup after it.

```go title="main.go" del={2} ins={3-8}
	app.OnShutdown(func(ctx context.Context) error {
		<-workers.Done()
		select {
		case <-workers.Done():
			return nil
		case <-ctx.Done():
			return ctx.Err()
		}
	})
```
:::

## When starting fails

A hook that returns an error stops the App from starting, and so does an address that's already in use. The App then shuts down whatever it set up so far, calling `Shutdown` on every provider that registered, and `Serve` returns the error together with any from shutting down. Nothing stays open behind a failed start.

## Run the App without a server

`Start` runs the boot, start and ready phases without listening, and `Shutdown` runs the shutdown phase. Tests and Commands that need the App's services use them:

```go title="posts_test.go"
func TestListPosts(t *testing.T) {
	app := newApp()
	if err := app.Start(t.Context()); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = app.Shutdown(context.Background()) })

	w := httptest.NewRecorder()
	app.ServeHTTP(w, httptest.NewRequest("GET", "/posts", nil))
	if w.Code != http.StatusOK {
		t.Errorf("GET /posts = %d", w.Code)
	}
}
```

`Boot`, `Start` and `Shutdown` each run once: calling one again does nothing. `app.State()` reports how far the App has come: `created`, `booted`, `started`, `ready`, `shutting down` or `stopped`.

See also: [Service providers](service-providers.md), [Dependency injection](dependency-injection.md), [App](../../reference/app.md)
