BreakingExpress

Build your individual SaaS on Linux with Vely

Vely combines excessive efficiency and the low footprint of C with the benefit of use and improved security of languages like PHP. It’s free and open supply software program, licensed underneath GPLv3 and LGPL 3 for libraries, so you possibly can even construct business software program with it.

Using Vely for SaaS

You can use Vely to create a multitenant internet software which you can run on the Internet as Software-as-a-Service (SaaS). Each person has a totally separate knowledge house from every other.

In this instance internet software, a person can join a pocket book service to create notes after which view and delete them. It demonstrates a number of know-how integrations in simply 310 strains of code throughout seven supply recordsdata. The applied sciences embrace:

  • MariaDB
  • Web browser
  • Apache
  • Unix sockets

How it really works

Here’s how the applying works from a person’s perspective. A code walk-through follows the photographs.

The app permits a person to create a brand new login by specifying an electronic mail tackle and password. You can fashion these any approach you want, resembling with CSS:

(Sergio Mijatovic, CC BY-SA 4.0)

Verify the person’s electronic mail:

(Sergio Mijatovic, CC BY-SA 4.0)

Each person logs in with their distinctive username and password:

(Sergio Mijatovic, CC BY-SA 4.0)

Once logged in, a person can add a word:

(Sergio Mijatovic, CC BY-SA 4.0)

A person can get a listing of notes:

(Sergio Mijatovic, CC BY-SA 4.0)

The app asks for affirmation earlier than deleting a word:

(Sergio Mijatovic, CC BY-SA 4.0)

After the person confirms, the word is deleted:

(Sergio Mijatovic, CC BY-SA 4.0)

Setup stipulations

Follow the set up directions on Vely.dev. It’s a fast course of that makes use of customary packaging instruments, resembling DNF, APT, Pacman, or Zypper.

Because they’re a part of this instance, you will need to set up Apache as an online server and MariaDB as a database.

After putting in Vely, activate syntax highlighting in Vim in case you’re utilizing it:

vv -m

Get the supply code

The supply code for this demonstration SaaS app is a part of the Vely set up. It’s a good suggestion to create a separate supply code listing for every software (and you may identify it no matter you want). In this case, unpacking the supply code does that for you:

$ tar xvf $(vv -o)/examples/multitenant_SaaS.tar.gz
$ cd multitenant_SaaS

By default, the applying is known as multitenant_SaaS, however you possibly can name it something (in case you do this, change it in all places).

Set up the applying

The very first step is to create an software. It’s easy to do with Vely’s vf utility:

$ sudo vf -i -u $(whoami) multitenant_SaaS

This command creates a brand new software dwelling (/var/lib/vv/multitenant_SaaS) and performs the applying setup for you. Mostly, which means creating varied subdirectories within the dwelling folder and assigning privileges. In this case, solely the present person (the results of whoami) owns the directories, with 0700 privileges, which ensures that nobody else has entry to the recordsdata.

Set up the database

Before doing any coding, you want a spot to retailer the data utilized by the applying. First, create a MariaDB database referred to as db_multitenant_SaaS, owned by the person vely with password your_password. You can change any of those values, however bear in mind to alter them in all places throughout this instance.

Logged in as root within the MySQL utility:

CREATE DATABASE IF NOT EXISTS db_multitenant_SaaS;
CREATE USER IF NOT EXISTS vely IDENTIFIED BY 'your_password';
GRANT CREATE,ALTER,DROP,SELECT,INSERT,DELETE,UPDATE ON db_multitenant_SaaS.* TO vely;

Then create database objects (tables and information and so forth) within the database:

USE db_multitenant_SaaS;
SOURCE setup.sql;
exit

Connect Vely to a database

To let Vely know the place your database is and methods to log into it, create a database config file named db_multitenant_SaaS. (This is the identify utilized by the database statements within the supply code, so in case you change it, be sure you change it in all places.)

Vely makes use of native MariaDB database connectivity, so you possibly can specify any choices {that a} given database permits you to:

$ echo '[client]
person=vely
password=your_password
database=db_multitenant_SaaS
protocol=TCP
host=127.0.0.1
port=3306'
> db_multitenant_SaaS

Build the applying

Use the vv utility to make the applying, utilizing the --db choice to specify the MariaDB database and the database config file:

$ vv -q --db=mariadb:db_multitenant_SaaS

Programming and improvement

Start the applying server

To begin the applying server to your internet software, use the vf FastCGI course of supervisor. The software server makes use of a Unix socket to speak with the online server (making a reverse proxy):

$ vf -w 3 multitenant_SaaS

This begins three daemon processes to serve the incoming requests. You may begin an adaptive server that will increase the variety of processes to serve extra requests and steadily scale back the variety of processes once they’re not wanted:

$ vf multitenant_SaaS

See vf for extra choices that will help you obtain one of the best efficiency.

When it is advisable cease your software server, use the -m give up choice:

$ vf -m give up multitenant_SaaS

Set up the online server

This is an online software, so the applying wants an online server. This instance makes use of Apache by the use of a Unix socket listener.

1. Set up Apache

To configure Apache as a reverse proxy and join your software to it, it is advisable allow FastCGI proxy help, which usually means utilizing the proxy and proxy_fcgi modules.

For Fedora methods (or others, like Arch) allow the proxy and proxy_fcgi modules by including (or uncommenting) the suitable LoadModule directives within the /and so on/httpd/conf/httpd.conf Apache configuration file.

For Debian, Ubuntu, and related methods, allow the proxy and proxy_fcgi modules:

$ sudo a2enmod proxy
$ sudo a2enmod proxy_fcgi

For OpenSUSE, add these strains to the tip of /and so on/apache2/httpd.conf:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

2. Configure Apache

Now you will need to add the proxy info to the Apache configuration file:

ProxyPass "/multitenant_SaaS" unix:///var/lib/vv/multitenant_SaaS/sock/sock|fcgi://localhost/multitenant_SaaS

The location of your configuration might range, relying in your Linux distribution:

  • Fedora, CentOS, Mageia, and Arch: /and so on/httpd/conf/httpd.conf
  • Debian, Ubuntu, Mint: /and so on/apache2/apache2.conf
  • OpenSUSE: /and so on/apache2/httpd.conf

3. Restart

Finally, restart Apache. On Fedora and related methods, in addition to Arch Linux:

$ sudo systemctl restart httpd

On Debian and Debian-based methods, in addition to OpenSUSE:

$ sudo systemctl restart apache2

Set up native mail

This instance makes use of electronic mail as part of its operate. If your server can already ship electronic mail, you possibly can skip this. Otherwise, you need to use native mail (myuser@localhost) simply to check it out. To do this, set up Sendmail.

On Fedora and related:

$ sudo dnf set up sendmail
$ sudo systemctl begin sendmail

On Debian methods (like Ubuntu):

$ sudo apt set up sendmail
$ sudo systemctl begin sendmail

When the applying sends an electronic mail to a neighborhood person, resembling OS_user@localhost, then you possibly can confirm that the e-mail was despatched by taking a look at /var/mail/ (the “mail spool”).

Access the applying server from the browser

Assuming you are operating the applying domestically, use http://127.0.0.1/multitenant_SaaS?req=notes&action=begin to entry your software server out of your internet browser. If you are operating this on a dwell server on the Internet, you could want to regulate your firewall settings to permit HTTP visitors.

Source code

This instance software incorporates seven supply recordsdata. You can evaluate the code your self (bear in mind, it is simply 310 strains throughout these recordsdata), however here is an summary of every one.

SQL setup (setup.sql)

The two tables created are:

  • customers: Information about every person. Each person within the customers desk has its personal distinctive ID (userId column) together with different info resembling electronic mail tackle and whether or not it is verified. There’s additionally a hashed password. An precise password is rarely saved in plain textual content (or in any other case); a one-way hash is used to verify the password.
  • notes: Notes entered by the person. The notes desk incorporates the notes, every together with userId column that states which person owns them. The userId column’s worth matches the namesake column from customers desk. This approach, each word clearly belongs to a single person.

The file contents:

CREATE TABLE IF NOT EXISTS notes (dateOf datetime, noteId BIGINT AUTO_INCREMENT PRIMARY KEY, userId BIGINT, word VARCHAR(1000));
CREATE TABLE IF NOT EXISTS customers (userId BIGINT AUTO_INCREMENT PRIMARY KEY, electronic mail VARCHAR(100), hashed_pwd VARCHAR(100), verified SMALLINT, verify_token VARCHAR(30), SESSION VARCHAR(100));
CREATE UNIQUE INDEX IF NOT EXISTS users1 ON customers (electronic mail);

Run-time knowledge (login.h)

To correctly show the Login, Sign Up, and Logout hyperlinks, you want some flags which are out there anyplace within the software. Also, the applying makes use of cookies to take care of a session, so this must be out there anyplace, for instance, to confirm that the session is legitimate. Every request despatched to the applying is confirmed that approach. Only requests that include verifiable cookies are permitted.

So to that impact, you could have a global_request_data kind reqdata (request knowledge) and in it there’s sess_userId (ID of person) and sess_id (person’s present session ID). You even have reasonably self-explanatory flags that assist render pages:

#ifndef _VV_LOGIN
#outline _VV_LOGIN

typedef struct s_reqdata {
    bool displayed_logout; // true if Logout hyperlink displayed
    bool is_logged_in; // true if session verified logged-in
    char *sess_userId; // person ID of present session
    char *sess_id; // session ID
} reqdata;

void login_or_signup ();

#endif

Session checking and session knowledge (_before.vely)

Vely has a notion of a before_request_handler. The code you write executes earlier than every other code that handles a request. To do that, all you want is to write down this code in a file named _before.vely, and the remainder is routinely dealt with.

Anything {that a} SaaS software does, resembling dealing with requests despatched to an software, should be validated for safety. This approach, the applying is aware of whether or not the caller has the permissions wanted to carry out an motion.

Checking for permission is finished right here in a before-request handler. That approach, no matter different code you could have dealing with a request, you have already got the session info.

To preserve session knowledge (like session ID and person ID) out there anyplace in your code, you utilize global_request_data. It’s only a generic pointer (void*) to reminiscence that any code that handles requests can entry. This is ideal for dealing with periods, as proven beneath:

#embrace "vely.h"
#embrace "login.h"

// _before() is a before-request-handler. It at all times executes earlier than
// every other code that handles a request. It's a superb place for any
// type of request-wide setting or knowledge initialization
void _before() {
    // Output HTTP header
    out-header default
    reqdata *rd; // that is international request knowledge, see login.h
    // allocate reminiscence for international request knowledge, might be routinely deallocated
    // on the finish of request
    new-mem rd measurement sizeof(reqdata)
    // initialize flags
    rd->displayed_logout = false;
    rd->is_logged_in = false;
    // set the information we created to be international request knowledge, accessible
    // from any code that handles a request
    set-req knowledge rd
    // verify if session exists (based mostly on cookies from the shopper)
    // this executes earlier than every other request-handling code, making it
    // simpler to only have session info prepared
    _check_session ();
}

Checking if the session is legitimate (_check_session.vely)

One of a very powerful duties in a multitenant SaaS software is to verify (as quickly as potential) if the session is legitimate by checking whether or not a person is logged in. It’s executed by getting the session ID and person ID cookies from the shopper (resembling an online browser) and checking these in opposition to the database the place periods are saved:

#embrace "vely.h"
#embrace "login.h"

// Check if session is legitimate
void _check_session () {
    // Get international request knowledge
    reqdata *rd;
    get-req knowledge to rd
    // Get cookies from person browser
    get-cookie rd->sess_userId="sess_userId"
    get-cookie rd->sess_id="sess_id"
    if (rd->sess_id[0] != 0) {
        // Check if session ID is appropriate for given person ID
        char *electronic mail;
        run-query @db_multitenant_SaaS = "select email from users where userId='%s' and session='%s'" output electronic mail : rd->sess_userId, rd->sess_id row-count outline rcount
            query-result electronic mail to electronic mail
        end-query
        if (rcount == 1) {
            // if appropriate, set logged-in flag
            rd->is_logged_in = true;
            // if Logout hyperlink not show, then show it
            if (rd->displayed_logout == false) {
                @Hi <<p-out electronic mail>>! <a href="https://opensource.com/?req=login&action=logout">Logout</a><br/>
                rd->displayed_logout = true;
            }
        } else rd->is_logged_in = false;
    }
}

Signing up, Logging in, Logging out (login.vely)

The foundation of any multitenant system is the power for a person to enroll, log in, and log off. Typically, signing up includes verifying the e-mail tackle; most of the time, the identical electronic mail tackle is used as a username. That’s the case right here.

There are a number of subrequests applied right here which are essential to carry out the performance:

  • When Signing Up a brand new person, show the HTML type to gather the data. The URL request signature for that is req=login&motion=newuser.
  • As a response to the Sign Up type, create a brand new person. The URL request signature is req=login&motion=createuser. The input-param sign obtains an electronic mail and pwd POST type fields. The password worth is a one-way hash, and an electronic mail verification token is created as a random five-digit quantity. These are inserted into the customers desk, creating a brand new person. A verification electronic mail is distributed, and the person is prompted to learn the e-mail and enter the code.
  • Verify the e-mail by coming into the verification code despatched to that electronic mail. The URL request signature is req=login&motion=confirm.
  • Display a Login type for the person to log in. The URL request signature is req=login (for example, motion is empty.)
  • Log in by verifying the e-mail tackle (username) and password. The URL request signature is req=login&motion=login.
  • Logout on the person’s request. The URL request signature is req=login&motion=logout.
  • Landing web page for the applying. The URL request signature is req=login&motion=start.
  • If the person is presently logged in, go to the applying’s touchdown web page.

See examples of those beneath:

#embrace "vely.h"
#embrace "login.h"

// Handle session upkeep, login, logout, session verification
// for any multitenant Cloud software
void login () {
    // Get URL enter parameter "action"
    input-param motion

    // Get international request knowledge, we document session info in it, so it is helpful
    reqdata *rd;
    get-req knowledge to rd

    // If session is already established, the one motive why we cannot proceed to
    // software dwelling is that if we're logging out
    if (rd->is_logged_in) {
        if (strcmp(motion, "logout")) {
            _show_home();
            exit-request
        }
    }

    // Application display to get began. Show hyperlinks to login or signup and present
    // dwelling display applicable for this
    if (!strcmp (motion, "begin")) {
        _show_home();
        exit-request

    // Start creating new person. Ask for electronic mail and password, then proceed to create person
    // when this type is submitted.
    } else if (!strcmp (motion, "newuser")) {
        @Create New User<hr/>
        @<type motion="https://opensource.com/?req=login" methodology="POST">
        @<enter identify="action" kind="hidden" worth="createuser">
        @<enter identify="email" kind="text" worth="" measurement="50" maxlength="50" required autofocus placeholder="Email">
        @<enter identify="pwd" kind="password" worth="" measurement="50" maxlength="50" required placeholder="Password">
        @<enter kind="submit" worth="Sign Up">
        @</type>

    // Verify code despatched to electronic mail by person. The code should match, thus verifying electronic mail tackle    
    } else if (!strcmp (motion, "verify")) {
        input-param code
        input-param electronic mail
        // Get confirm token based mostly on electronic mail
        run-query @db_multitenant_SaaS = "select verify_token from users where email="%s"" output db_verify : electronic mail
            query-result db_verify to outline db_verify
            // Compare token recorded in database with what person offered
            if (!strcmp (code, db_verify)) {
                @Your electronic mail has been verifed. Please <a href="https://opensource.com/?req=login">Login</a>.
                // If matches, replace person information to point it is verified
                run-query @db_multitenant_SaaS no-loop = "update users set verified=1 where email="%s"" : electronic mail
                exit-request
            }
        end-query
        @Could not confirm the code. Please attempt <a href="https://opensource.com/?req=login">again</a>.
        exit-request

    // Create person - this runs when person submits type with electronic mail and password to create a person    
    } else if (!strcmp (motion, "createuser")) {
        input-param electronic mail
        input-param pwd
        // create hashed (one-way) password
        hash-string pwd to outline hashed_pwd
        // generate random 5 digit string for confirm code
        random-string to outline confirm size 5 quantity
        // create person: insert electronic mail, hashed password, verification token. Current confirm standing is 0, or not verified
        begin-transaction @db_multitenant_SaaS
        run-query @db_multitenant_SaaS no-loop = "insert into users (email, hashed_pwd, verified, verify_token, session) values ('%s', '%s', '0', '%s', '')" : electronic mail, hashed_pwd, confirm affected-rows outline arows error outline err on-error-continue
        if (strcmp (err, "0") || arows != 1) {
            // if can't add person, it most likely would not exist. Either approach, we won't proceed.
            login_or_signup();
            @User with this electronic mail already exists.
            rollback-transaction @db_multitenant_SaaS
        } else {
            // Create electronic mail with verification code and electronic mail it to person
            write-string outline msg
                @From: vely@vely.dev
                @To: <<p-out electronic mail>>
                @Subject: confirm your account
                @
                @Your verification code is: <<p-out confirm>>
            end-write-string
            exec-program "/usr/sbin/sendmail" args "-i", "-t" enter msg standing outline st
            if (st != 0) {
                @Could not ship electronic mail to <<p-out electronic mail>>, code is <<p-out confirm>>
                rollback-transaction @db_multitenant_SaaS
                exit-request
            }
            commit-transaction @db_multitenant_SaaS
            // Inform the person to go verify electronic mail and enter verification code
            @Please verify your electronic mail and enter verification code right here:
            @<type motion="https://opensource.com/?req=login" methodology="POST">
            @<enter identify="action" kind="hidden" worth="verify" measurement="50" maxlength="50">
            @<enter identify="email" kind="hidden" worth="<<p-out email>>">
            @<enter identify="code" kind="text" worth="" measurement="50" maxlength="50" required autofocus placeholder="Verification code">
            @<button kind="submit">Verify</button>
            @</type>
        }

    // This runs when logged-in person logs out.    
    } else if (!strcmp (motion, "logout")) {
        // Update person desk to wipe out session, that means no such person is logged in
        if (rd->is_logged_in) {
            run-query @db_multitenant_SaaS = "update users set session='' where userId='%s'" : rd->sess_userId no-loop affected-rows outline arows
            if (arows == 1) {
                rd->is_logged_in = false; // point out person not logged in
                @You have been logged out.<hr/>
            }
        }
        _show_home();

    // Login: this runs when person enters person identify and password
    } else if (!strcmp (motion, "login")) {
        input-param pwd
        input-param electronic mail
        // create one-way hash with the intention of evaluating with person desk - password is NEVER recorded
        hash-string pwd to outline hashed_pwd
        // create random 30-long string for session ID
        random-string to rd->sess_id size 30
        // Check if person identify and hashed password match
        run-query @db_multitenant_SaaS = "select userId from users where email="%s" and hashed_pwd='%s'" output sess_userId : electronic mail, hashed_pwd
            query-result sess_userId to rd->sess_userId
            // If match, replace person desk with session ID
            run-query @db_multitenant_SaaS no-loop = "update users set session='%s' where userId='%s'" : rd->sess_id, rd->sess_userId affected-rows outline arows
            if (arows != 1) {
                @Could not create a session. Please attempt once more. <<.login_or_signup();>> <hr/>
                exit-request
            }
            // Set person ID and session ID as cookies. User's browser will return these to us with each request
            set-cookie "sess_userId" = rd->sess_userId
            set-cookie "sess_id" = rd->sess_id
            // Display dwelling, be sure session is appropriate first and set flags
            _check_session();
            _show_home();
            exit-request
        end-query
        @Email or password are usually not appropriate. <<.login_or_signup();>><hr/>

    // Login display, asks person to enter person identify and password    
    } else if (!strcmp (motion, "")) {
        login_or_signup();
        @Please Login:<hr/>
        @<type motion="https://opensource.com/?req=login" methodology="POST">
        @<enter identify="action" kind="hidden" worth="login" measurement="50" maxlength="50">
        @<enter identify="email" kind="text" worth="" measurement="50" maxlength="50" required autofocus placeholder="Email">
        @<enter identify="pwd" kind="password" worth="" measurement="50" maxlength="50" required placeholder="Password">
        @<button kind="submit">Go</button>
        @</type>
    }
}

// Display Login or Sign Up hyperlinks
void login_or_signup() {
        @<a href="https://opensource.com/?req=login">Login</a> & & <a href="https://opensource.com/?req=login&action=newuser">Sign Up</a><hr/>
}

General-purpose software (_show_home.vely)

With this tutorial, you possibly can create any multitenant SaaS software you need. The multitenant-processing module above (login.vely) calls the _show_home() operate, which may home any code of yours. This instance code exhibits the Notes software, however it may very well be something. The _show_home() operate calls any code you would like and is a general-purpose multitenant software plug-in:

#embrace "vely.h"

void _show_home() {
    notes();
    exit-request
}

Notes software (notes.vely)

The software is ready to add, record, and delete any given word:

#embrace "vely.h"
#embrace "login.h"

// Notes software in a multitenant Cloud
void notes () {
    // get international request knowledge
    reqdata *rd;
    get-req knowledge to rd
    // If session invalid, show Login or Signup
    if (!rd->is_logged_in) {
        login_or_signup();
    }
    // Greet the person
    @<h1>Welcome to Notes!</h1><hr/>
    // If not logged in, exit - this ensures safety verification of person's id
    if (!rd->is_logged_in) {
        exit-request
    }
    // Get URL parameter that tells Notes what to do
    input-param subreq
    // Display actions that Notes can do (add or record notes)
    @<a href="https://opensource.com/?req=notes&subreq=add">Add Note</a> <a href="https://opensource.com/?req=notes&subreq=list">List Notes</a><hr/>

    // List all notes for this person
    if (!strcmp (subreq, "list")) {
        // choose notes for this person ONLY
        run-query @db_multitenant_SaaS = "select dateOf, note, noteId from notes where userId='%s' order by dateOf desc" : rd->sess_userId output dateOf, word, noteId
            query-result dateOf to outline dateOf
            query-result word to outline word
            query-result noteId to outline noteId
            // change new strains to <br/> with quick cached Regex
            match-regex "n" in word replace-with "<br/>n" end result outline with_breaks standing outline st cache
            if (st == 0) with_breaks = word; // nothing was discovered/changed, simply use unique
            // Display a word
            @Date: <<p-out dateOf>> (<a href="https://opensource.com/?req=notes&subreq=delete_note_ask&note_id=%3C%3Cp-out%20noteId%3E%3E">delete word</a>)<br/>
            @Note: <<p-out with_breaks>><br/>
            @<hr/>
        end-query
    }

    // Ask to delete a word
    else if (!strcmp (subreq, "delete_note_ask")) {
        input-param note_id
        @Are you positive you wish to delete a word? Use Back button to return, or <a href="https://opensource.com/?req=notes&subreq=delete_note&note_id=%3C%3Cp-out%20note_id%3E%3E">delete word now</a>.
    }

    // Delete a word
    else if (!strcmp (subreq, "delete_note")) {
        input-param note_id
        // Delete word
        run-query @db_multitenant_SaaS = "delete from notes where noteId='%s' and userId='%s'" : note_id, rd->sess_userId affected-rows outline arows no-loop error outline errnote
        // Inform person of standing
        if (arows == 1) {
            @Note deleted
        } else {
            @Could not delete word (<<p-out errnote>>)
        }
    }

    // Add a word
    else if (!strcmp (subreq, "add_note")) {
        // Get URL POST knowledge from word type
        input-param word
        // Insert word underneath this person's ID
        run-query @db_multitenant_SaaS = "insert into notes (dateOf, userId, note) values (now(), '%s', '%s')" : rd->sess_userId, word affected-rows outline arows no-loop error outline errnote
        // Inform person of standing
        if (arows == 1) {
            @Note added
        } else {
            @Could not add word (<<p-out errnote>>)
        }
    }

    // Display an HTML type to gather a word, and ship it again right here (with subreq="add_note" URL param)
    else if (!strcmp (subreq, "add")) {
        @Add New Note
        @<type motion="https://opensource.com/?req=notes" methodology="POST">
        @<enter identify="subreq" kind="hidden" worth="add_note">
        @<textarea identify="note" rows="5" cols="50" required autofocus placeholder="Enter Note"></textarea>
        @<button kind="submit">Create</button>
        @</type>
    }
}

SaaS with C efficiency

Vely makes it potential to leverage the facility of C in your internet purposes. A multitenant SaaS software is a major instance of a use case that advantages from that. Take a take a look at the code examples, write some code, and provides Vely a attempt.

Exit mobile version