Application code forgets to set updated_at, especially when three different places write the same row. A trigger cannot forget. One function for the whole database, one trigger per table.
Snippet
Copied 10 times
alter table posts
add column if not exists updated_at timestamptz not null default now();
create or replace function set_updated_at()
returns trigger language plpgsql as $$
begin
new.updated_at = now();
return new;
end;
$$;
create trigger posts_set_updated_at
before update on posts
for each row execute function set_updated_at();
-- Repeat only the trigger for each table; the function is shared.