Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Márton Papp
Márton Papp

Posted on

     

Knex ❤️ PSQL: updating timestamps like a pro

Knex.js is the most popular SQL query builder around and the go-to solution for most of us working with PostgreSQL. You can find dozens of articles on dev.to about how to get started, so I decided to focus on a more advanced and often overlooked topic on how to keep theupdated_at fieldsreally updated - automatically.

What does table.timestamps() do?

If you read along the documentation, upon creating a new table you will probably write a migration like this:

exports.up=function(knex){returnknex.schema.createTable('products',function(table){table.increments('id').primary();table.string('name');table.timestamps(false,true);});};

Thetable.timestamps(false, true) line addscreated_at andupdated_at columns on the table. Both columns default to being not null and using the current timestamp whentrue is passed as the second argument.
While it's sufficent for thecreated_at column, theupdated_at will remain unchanged even after an update query executed and it's your responsibility to keep it in sync.

There is a good reason behind this behaviour: different SQL dialects - like MySQL - handle automatic updatingpretty well, but others, like PostgreSQL don't support it.

What is a Trigger Procedure in PSQL?

Think of a trigger procedures like middlewares inexpressjs. You have the opportunity to execute functions that modify the inserted valuesbefore actually committing the update. TheNEW value holds the new database row for INSERT/UPDATE operations, so setting theupdated_at field is really easy:

BEGINNEW.updated_at=CURRENT_TIMESTAMP;RETURNNEW;END;

Okay, okay, just give me the code already

First you need to create this trigger function in a migration usingknex.raw:

exports.up=function(knex){returnknex.raw(`    CREATE OR REPLACE FUNCTION update_timestamp() RETURNS TRIGGER    LANGUAGE plpgsql    AS    $$    BEGIN        NEW.updated_at = CURRENT_TIMESTAMP;        RETURN NEW;    END;    $$;  `);};exports.down=function(knex){returnknex.raw(`    DROP FUNCTION IF EXISTS update_timestamp() CASCADE;  `);};

To make sure everything went fine execute the following query:

SELECTroutine_name,routine_definitionFROMinformation_schema.routinesWHEREroutine_type='FUNCTION'ANDspecific_schema='public';+------------------+---------------------------------------------+|routine_name|routine_definition||------------------+---------------------------------------------||update_timestamp||||BEGIN|||NEW.updated_at=CURRENT_TIMESTAMP;|||RETURNNEW;|||END;||||+------------------+---------------------------------------------+

But how to use the function?

This function alone does nothing, we also need to tell the database engine where and when to use it. The best place for this the upcoming migrations where you create a new table - going back to my first example the code will be this:

consttableName='products';exports.up=asyncfunction(knex){awaitknex.schema.createTable(tableName,function(table){table.increments('id').primary();table.string('name');table.timestamps(false,true);});awaitknex.raw(`    CREATE TRIGGER update_timestamp    BEFORE UPDATE    ON${tableName}    FOR EACH ROW    EXECUTE PROCEDURE update_timestamp();  `);};exports.down=function(knex){returnknex.schema.dropTable(tableName);};

If you run the\d products command, at the bottom of the table you will see that the trigger function will be executed on each row update on this table.

>\dproducts+------------+--------------------------+--------------------------------------------------------+|Column|Type|Modifiers||------------+--------------------------+--------------------------------------------------------||id|integer|notnulldefaultnextval('products_id_seq'::regclass)||name|charactervarying(255)|||created_at|timestampwithtimezone|notnulldefaultnow()||updated_at|timestampwithtimezone|notnulldefaultnow()|+------------+--------------------------+--------------------------------------------------------+Indexes:"products_pkey"PRIMARYKEY,btree(id)Triggers:update_timestamp_on_productsBEFOREUPDATEONproductsFOREACHROWEXECUTEPROCEDUREupdate_timestamp()

As always, your likes and feedbacks are much appreciated!

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
cryptodoct0r profile image
EMMANUEL NENI
Medical Student by day, Full Stack Developer by Night, Love to learn new stuff from all calls of life, like to walk & Occasionally play the Violin.
  • Location
    Ukraine
  • Education
    MBBS
  • Joined

For anyone confused about the triger function,

If you have multiple migration files in a set order, you might need to artificially change the date stamp in the filename to get this to run first (or just add it to your first migration file).

I suggest moving the already made migrations, create anpx knex migrate:make tigger_updated_at them past the trigger funciton here and remake your sequence of migrations so the trigger function works first.

But if you can't roll back, you might need to do this step manually via psql.

Thankyou for the post. It was quit informative

CollapseExpand
 
maria_brezhneva_da8a4e630 profile image
Maria Brezhneva
  • Joined

Thank you!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Javascript Developer💪 Node.JS, VueJS, Electron👌 PSQL, MongoDB, RabbitMQ😎 Can exit VIM
  • Location
    Hungary
  • Joined

More fromMárton Papp

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp