Use database

Store data into a relational database

Use database

Storing the Message in the Database

We are ready to use the database that we enabled at the beginning of the tutorial.

Since we are using a relational database, we need to create a table to store the contact data. We can do that by creating a new action called create-table.js in the packages/contact folder:

const { Client } = require('pg')

async function main(args) {
    const client = new Client({ connectionString: args.dbUri });

    const createTable = `
    CREATE TABLE IF NOT EXISTS contacts (
        id serial PRIMARY KEY,
        name varchar(50),
        email varchar(50),
        phone varchar(50),
        message varchar(300)
    );
    `
    // Connect to database server
    await client.connect();
    console.log('Connected to database');

    try {
        await client.query(createTable);
        console.log('Contact table created');
    } catch (e) {
        console.log(e);
        throw e;
    } finally {
        client.end();
    }
}

We just need to run this once, therefore it doesn’t need to be a web action. Here we can take advantage of the cron service we enabled! There are also a couple of console logs that we can check out.

With the cron scheduler you can annotate an action with 2 kinds of labels. One to make OpenServerless periodically invoke the action, the other to automatically execute an action once, on creation.

Let’s create the action with the latter, which means annotating the action with autoexec true:

ops action create contact/create-table create-table.js -a autoexec true
ok: created action contact/create-table

With -a you can add “annotations” to an action. OpenServerless will invoke this action as soon as possible, so we can go on.

In OpenServerless an action invocation is called an activation. You can keep track, retrieve information and check logs from an action with ops activation. For example, with:

ops activation list

You can retrieve the list of invocations. For caching reasons the first time you run the command the list might be empty. Just run it again and you will see the latest invocations (probably some hello actions from the deployment).

If we want to make sure create-table was invoked, we can do it with this command. The cron scheduler can take up to 1 minute to run an autoexec action, so let’s wait a bit and run ops activation list again.

ops activation list

Datetime            Activation ID                    Kind      Start Duration   Status  Entity
2023-10-02 09:52:01 1f02d3ef5c32493682d3ef5c32b936da nodejs:18 cold  312ms      success openserverless/create-table:0.0.1
..

Or we could run ops activation poll to listen for new logs.

ops activation poll

Enter Ctrl-c to exit.
Polling for activation logs

When the logs from the create-table action appear, we can stop the command with Ctrl-c.

Each activation has an Activation ID which can be used with other ops activation subcommands or with the ops logs command.

We can also check out the logs with either ops logs <activation-id> or ops logs --last to quickly grab the last activation’s logs:

ops logs --last

2023-10-15T14:41:01.230674546Z stdout: Connected to database
2023-10-15T14:41:01.238457338Z stdout: Contact table created

The Action to Store the Data

We could just write the code to insert data into the table in the submit.js action, but it’s better to have a separate action for that.

Let’s create a new file called write.js in the packages/contact folder:

const { Client } = require('pg')

async function main(args) {
    const client = new Client({ connectionString: args.dbUri });

    // Connect to database server
    await client.connect();

    const { name, email, phone, message } = args;

    try {
        let res = await client.query(
            'INSERT INTO contacts(name,email,phone,message) VALUES($1,$2,$3,$4)',
            [name, email, phone, message]
        );
        console.log(res);
    } catch (e) {
        console.log(e);
        throw e;
    } finally {
        client.end();
    }

    return {
        body: args.body,
        name,
        email,
        phone,
        message
        };
}

Very similar to the create table action, but this time we are inserting data into the table by passing the values as parameters. There is also a console.log on the response in case we want to check some logs again.

Let’s deploy it:

ops action create contact/write write.js
ok: created action contact/write

Finalizing the Submit

Alright, we are almost done. We just need to create a pipeline of submitwrite actions. The submit action returns the 4 form fields together with the HTML body. The write action expects those 4 fields to store them. Let’s put them together into a sequence:

ops action create contact/submit-write  --sequence contact/submit,contact/write --web true
ok: created action contact/submit-write

With this command we created a new action called submit-write that is a sequence of submit and write. This means that OpenServerless will call in a sequence submit first, then get its output and use it as input to call write.

Now the pipeline is complete, and we can test it by submitting the form again. This time the data will be stored in the database.

Note that write passes on the HTML body so we can still see the thank you message. If we want to hide it, we can just remove the body property from the return value of write. We are still returning the other 4 fields, so another action can use them (spoiler: it will happen next chapter).

Let’s check out again the action list:

ops action list

actions
/openserverless/contact/submit-write                  private sequence
/openserverless/contact/write                         private nodejs:18
/openserverless/contact/create-table                  private nodejs:18
/openserverless/contact/submit                        private nodejs:18

You probably have something similar. Note the submit-write is managed as an action, but it’s actually a sequence of 2 actions. This is a very powerful feature of OpenServerless, as it allows you to create complex pipelines of actions that can be managed as a single unit.

Trying the Sequence

As before, we have to update our index.html to use the new action. First let’s get the URL of the submit-write action:

ops url contact/submit-write
<apihost>/api/v1/web/openserverless/contact/submit-write

Then we can update the index.html file:

---     <form method="POST" action="/api/v1/web/openserverless/contact/submit"
              enctype="application/x-www-form-urlencoded"> <-- old
+++     <form method="POST" action="/api/v1/web/openserverless/contact/submit-write"
              enctype="application/x-www-form-urlencoded"> <-- new

We just need to add -write to the action name.

Try again to fill the contact form (with correct data) and submit it. This time the data will be stored in the database.

If you want to retrive info from you database, ops provides several utilities under the ops devel command. They are useful to interact with the integrated services, such as the database we are using.

For instance, let’s run:

ops devel psql sql "SELECT * FROM CONTACTS"

[{'id': 1, 'name': 'OpenServerless', 'email': 'info@nuvolaris.io', 'phone': '5551233210', 'message': 'This is awesome!'}]


Last modified August 31, 2024: Merge all work done until now (#23) (3327e3a)