Folder structure
This guide covers the folder structure of an App created from the skeleton starter kit. You will learn:
- What each top-level directory holds
- How
main.goandstartassemble the App - Where your own code goes as the App grows
- Which package may import which
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.
blog/
├── app/
│ ├── exceptions/
│ └── handlers/
├── config/
├── start/
├── tests/
│ └── functional/
├── main.go
├── go.mod
├── .env.example
└── .env.testThe 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:
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:
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:
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:
mainimportsstart.startimportsconfigandapp/....app/...packages import Tinka, and each other where one builds on another, but neverstartormain.
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