Skip to main content
Linkly’s links live in state, which you set to in-memory back in Chapter 1. Restart the engine and everything is gone. In this chapter you add a database worker (SQLite) that holds the durable record of links and a timestamped row for every click on a short code. state stays in the picture as a fast read cache in front of the database.
state can also persist on its own (store_method: file_based with a file_path). This chapter uses a dedicated database worker instead, which gives you durable storage plus SQL to query it.

Add the database worker

State is a fast cache, but you also want a durable record you can run SQL over: every link, and a timestamped row each time someone follows one. Uncomment the Ch. 3 block in worker-compose.yaml, and uncomment the start_after line under link:
worker-compose.yaml
For simplicity and ease of configuration we’ve pre-included the database configuration and are instructing you to uncomment entries and restart the entire project. This is not necessary, you can also:
  1. Add a worker with iii trigger compose::add worker=database
  2. Configure it in worker-compose.yaml
  3. Restart just that worker with iii trigger compose::restart worker=database.
In this way you can modify a running system without restarting the entire thing.
The database worker supports more than SQLite, refer to the database worker docs for all supported databases.
Restart the project with:
The worker will be in charge of defining its own schema. We’ll build up the necessary changes to link/src/index.ts in pieces.

Define the database

First add the DB constant near the top of link/src/index.ts:
src/index.ts
Now we’re going to adapt the existing link::create and link::resolve functions so that they write and read from our new database while using our state worker as a hot cache.

Create a schema

Add an ensureSchema() function to link/src/index.ts that creates both tables on startup. The database worker accepts SQL through its database::execute function. The database worker can register a moment after link, so add a few retries:
start_after in worker-compose.yaml controls worker start order. In this case we are having the link worker start_after database. This helps with reliability but it does not guarantee that the database worker will be ready when the link worker starts.
src/index.ts

Setup database writing

Modify link::create to write to both the database (durable record) and state (hot cache):
src/index.ts

Setup database retrieval

Modify link::resolve to check the cache first; on a miss, fall back to the database and warm the cache for the next read. It’s easiest to replace the existing link::resolve function with our new version:
src/index.ts

Add click tracking

Since we have a database now, you can start click tracking. Make a new function (link::record_click) to do that and save it to the database. The next chapter will move this work onto a queue so that it can run without touching the redirect’s logic. Add it below link::resolve:
src/index.ts

Update http::redirect to call link::record_click

Now update http::redirect to trigger it directly, right before returning the redirect:
src/index.ts
The database write for clicks adds latency to every redirect. The next chapter moves it onto a durable queue that removes the latency while also adding recovery from database failures.

Try the click tracking

Now let’s see the click tracking in action. Save the file, create a few links, and simulate a click on each:
The durable history is now queryable with SQL:

Conclusion

Did you know that --help works with function id’s as well? Try running: iii trigger database::query --help to see what arguments database::query accepts.
Linkly’s links are now durable: the database is the source of truth, state keeps lookups fast, and every redirect appends a timestamped row to the clicks table. But that row is written on the redirect’s hot path, so a slow database write slows the redirect. Next, in Ch. 4: Make it durable, you move that write onto a queue so redirects stay fast.