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. Add the database worker:
The engine has been running since Chapter 1, so it starts the database worker as soon as it is added, and the worker’s settings are managed in ./config/database.yaml from that point on (see Configuration). Adjust the fields under value: in that file so it looks like the below and save; the change applies without a restart.
The database worker will automatically create ./data/iii.db on first run.
The database worker supports more than SQLite, refer to the database worker docs for all supported databases.
config/database.yaml
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 at the end of link/src/index.ts that creates both tables on startup. The database worker accepts SQL through its database::execute function:
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 link, and simulate clicking it a few times:
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.