Chapter 3

Your First Caddy App

Intro

Caddy apps are akin to long-running processes. They can be considered long-running services, servers, or singleton data-holders (as I’ve seen in some implementations). A Caddy App is defined as a type that implements the following interface (in addition to caddy.Module):

type App interface {
    Start() error
    Stop() error
}

Caddy apps are Caddy modules that require the implementation of the caddy.App interface 1. The same lifecycle model of Caddy modules applies to any app, and also the same model of host/guest module structure is possible.

Exercise 1

Develope an app that watches a defined file/directory and runs a defined command with arguments when an event is detected. Possible config structure is:

{
  "app": {
    "watch": {
      "subject": "/path/to/watched/dir",
      "reaction": {
        "cmd": "hugo",
        "args": ["--config=/path/conf.yml", "-d=/path/to/destination"]
      }
    }
  }
}

Cross-App Relationships

It is possible for apps to form dependency relationship. This can be intuited by deliberating how Caddy is automagically able to manage TLS, although TLS management is the responsibility of the tls app. The caddy.Context object has a ctx.App("app_name") method, which allows the caller to retrieve or instantiate the named app module. If the app exists and is configured, it will be loaded, provisioned, then handed to the caller. If it is not configured, an empty instance will be loaded and handed to the caller. This is allowed with caveats:

  • The module cannot ask for its parent app

  • There cannot be circular dependencies across the apps

Exercise 2

Develop a handler module called hugo that uses the app built in Exercise 1 to have a watcher for the directory of hugo site to automate the generation of the website on changes. Bonus points if you obviate the need for subsequent file_server handler. You can study the github.com/abiosoft/caddy-named-routes module for inspiration.

Here’s the imagined config of such handler module:

{
	"apps": {
		"http": {
			"servers": {
				"srv0": {
					"listen": [
						":443"
					],
					"routes": [
						{
							"match": [
								{
									"host": [
										"localhost"
									]
								}
							],
							"handle": [
								{
									"handler": "hugo",
									"directory": "/path/to/hugo/dir",
									"dest": "/output/dir"
								}
							]
						}
					]
				}
			}
		}
	}
}

Or:

{
	"apps": {
		"http": {
			"servers": {
				"srv0": {
					"listen": [
						":443"
					],
					"routes": [
						{
							"match": [
								{
									"host": [
										"localhost"
									]
								}
							],
							"handle": [
								{
									"handler": "hugo",
									"directory": "/path/to/hugo/dir",
									"dest": "/output/dir"
								},
								{
									"handler": "file_server",
									"root": "/output/dir"
								}
							]
						}
					]
				}
			}
		}
	}
}