tinka alpha

Folder structure

This guide covers the folder structure of an App created from the skeleton starter kit. You will learn:

Overview

The skeleton gives every App the same shape, so you, your team, the docs and generators such as act make:handler all know where things go. Every directory is a Go package, imported by the App's module path, such as blog/start.

Text
blog/
├── app/
│   ├── exceptions/
│   └── handlers/
├── config/
├── start/
├── tests/
│   └── functional/
├── main.go
├── go.mod
├── .env.example
└── .env.test

The core starter kit has none of this: a single main.go, to structure as you choose.

main.go

main.go is the App's entry point. It builds the App with start.App, and hands it the command line:

main.go
package main

import (
	"fmt"
	"os"

	"blog/start"
)

func main() {
	app, err := start.App()
	if err != nil {
		fmt.Fprintln(os.Stderr, err) // every missing or invalid variable, at once
		os.Exit(1)
	}
	os.Exit(app.Main(os.Args[1:]))
}

One binary does everything: with no arguments it serves requests, and with arguments it runs one of the App's Commands, such as ./blog about. act runs the same binary for you during development.

start

start assembles the App, in three files:

start/app.gofile

App reads the configuration, creates the App and its services, and adds the middleware and routes. main runs the App it returns, and tests build it the same way.

start/kernel.gofile

Kernel adds the middleware every request passes through, in order.

start/routes.gofile

Routes registers the App's routes. It runs when the App starts.

app

app holds your code, in a package for each kind: app/handlers for handlers, app/middleware for middleware, app/providers for service providers, app/commands for Commands, and app/exceptions, whose Handler decides how errors reach clients. A new App has handlers and exceptions; the others appear as you add to them.

config

config reads the App's configuration from the environment, once, at boot, into typed structs: config/app.go for the App's URL and key, config/http.go for the HTTP server, config/logger.go for logging. config.Load reports every missing or invalid variable together, so a misconfigured App stops at boot. See Configuration & Environment.

.env.example lists the variables the App reads, without their values. .env, which tinka new creates with a fresh APP_KEY, holds your machine's values and stays out of git. .env.test holds the values tests use, and is committed.

tests

tests holds what tests share. tests.App builds the App as main does, in the test Mode, starts it and shuts it down when the test ends. HTTP tests that exercise the whole App go in tests/functional:

tests/functional/home_test.go
package functional_test

import (
	"net/http/httptest"
	"strings"
	"testing"

	"blog/tests"
)

func TestHome(t *testing.T) {
	app := tests.App(t)
	w := httptest.NewRecorder()
	app.ServeHTTP(w, httptest.NewRequest("GET", "/", nil))
	if w.Code != 200 || !strings.Contains(w.Body.String(), `"hello":"world"`) {
		t.Errorf("GET / = %d %s", w.Code, w.Body)
	}
}

Tests of a single package go next to it, as Go tests do, such as app/handlers/home_test.go. Run them all with act test.

Which package imports which

Go refuses two packages that import each other, so the skeleton's packages depend in one direction:

A handler that needs a service gets it from the App's container, with c.Make, rather than importing the package that creates it. That keeps every dependency pointing toward start, where the App is assembled.

See also: Installation, Configuration & Environment, Application lifecycle