December 11, 2023

How to Streamline Your Web Hosting

Tim McCallum Tim McCallum

spin python sqlite

How to Streamline Your Web Hosting

Are you tired of the constant headaches that come with web hosting? Read about how to free yourself from the hassle of SSL certificate renewals, database administration, web server management and firewall configuration. Learn how Fermyon Cloud effortlessly tackles each traditional web hosting challenge, with the intent of helping you minimize your costs and labor.

For web hosting, there are different types of plans, such as shared hosting, and dedicated servers, with managed hosting potentially costing up to $200/month. Beyond the hosting costs, there are other expenses associated with running a web application. In this first section, we will look at a few of these expenses and the effort required to set up and maintain a web application. We will also propose alternatives to traditional approaches and recommend Fermyon Cloud as a way to streamline your web hosting. The latter half of this article is a practical exercise where we create a web application and deploy it to Fermyon Cloud; you can follow along and create your own!

SSL Certificates

SSL certificates are used to make sure that when you visit a website, it’s actually the site you intended to visit and not a fake one. Sites with SSL certificates also provide an assurance that the information you send and receive (from that specific website) is private and secure. For example, when you are entering your credit card details while online shopping.

On some hosted platforms SSL certificates can cost up to $149 per year. In some cases, purchasing the certificates and contacting the platform staff to request the certificate renewal is a manual yearly requirement. This Forbes Web Hosting Cost Guide 2023 notes that some hosting plans can require an additional cost of between $10 to $200 per year for SSL certificates.

At Fermyon, we believe that it is a foregone conclusion that your web application will need SSL certificates installed and renewed on an ongoing basis. This is why Fermyon Cloud provides SSL certificates for your web applications for free. In addition, we automatically renew the SSL certificates for the lifetime of your application for free! You don’t even have to think about SSL certificates. All Fermyon Cloud applications are deployed using secure HTTP (HTTPS).

Databases

Databases store and manage information, including customer records, product details, orders, and more. Without a database, handling this information would be extremely challenging. Similarly, setting up and managing a database also presents its own set of challenges. When creating a web application, we begin by asking ourselves key questions: Where should we host the database? How should we manage the software upgrades for the database? And, how do we back up our application’s data effectively?

Realizing that the database component of your web application poses a considerable challenge, Fermyon Cloud offers a software stack where the database infrastructure already exists. You do not have to perform any installation or service provisioning to use a relational database as part of your web applications. Everything is taken care of for you.

Fermyon Cloud Starter tier offers up to 1 GB of SQLite database storage for $0/month. Want non-relational storage? The Starter tier also includes 5 key-value data stores with up to 1 GB of total storage.

As part of this article, we are going to provide a concrete example of how you can set up an SQLite relational database as part of a web application that runs on Fermyon Cloud. For an added twist, we will be writing the web application in Python. The example includes all commands, code and configuration so feel free to follow along in the latter half of this article.

Website Hosting

Running your own machines to host web applications can be costly and time-consuming. This is part of the reason that Virtual Machines (VMs) and Virtual Private Server (VPS) solutions have gained popularity over the last decade or so. The aforementioned Forbes Web Hosting Cost Guide 2023 reveals that Virtual Private Server (VPS) hosting can cost between $20 and $100 per month. (With VPS, users still share server space with other websites.) An online resource “What does it cost to build and host a web application in 2023?” shows web application development cost - hosting estimates can range from under $100/month for micro applications to over $100,000/month for large applications.

Understandably, you may not want to manage a server. For this reason, Fermyon Cloud offers you an alternative. A convenient and efficient platform to run your web applications, providing quick setup, an easy-to-use dashboard for management, and a foundation of robust open-source tools.

Fermyon Cloud Starter tier offers up to 5 applications and 100,000 request executions for $0/month.

As mentioned above, you will soon have an opportunity to take part in a practical exercise where we create a web application and deploy it to Fermyon Cloud.

Security

Security specialists (who perhaps sometimes fall under the broad category of a webmaster) are tasked with crucial responsibilities such as maintaining and updating machine firewalls to protect against cyber threats, and regularly upgrading software packages to ensure the website’s security and optimal performance.

Fermyon Cloud is secure by design, with each application running in its own sandboxed environment. This is one of the key benefits of using WebAssembly (Wasm) powered web applications on multi-tenanted platforms. Fermyon Cloud is designed to remove the need for you to handle any critical aspects of hosting a web application. For example, you are not required to implement and maintain firewalls, security, and load balancing on behalf of your application’s users. Security is part of the integrated cloud service offerings.

Revolutionizing Web Hosting: Building Python & SQLite Apps with Wasm on Fermyon Cloud

streamline your web hosting

It is time to build our web application example. We will show you how easy it is to create configure and then deploy the web application to Fermyon Cloud.

I chose a calendar application for the web application example, for a few reasons. Firstly because this is something that I see as being very useful on the (shared) web. Secondly, the calendar idea allows us to dive deep into the technical side of creating a Wasm-powered web application with a relational database backend. Thirdly, hosting an application for the web using Python, SQLite and Wasm is super interesting and new (and as it turns out extremely efficient). Lastly, you might scratch your head and wonder about the financial side of hosting a useful application (a step above just hosting web pages). For example, you could:

  • move your current web presence over to Fermyon Cloud to reduce your outgoings, and/or
  • consider building something new (develop your own application idea), release it into the wild and potentially turn a profit.

Disclaimer: This is not financial advice. This article is to written primarily to inspire you to streamline any sort of web hosting through the use of new technologies (like Wasm).

The Calendar Application Idea

The calendar application idea can be used for anything from a family calendar to a group or organization’s calendar whereby the application stores and shares the scheduling related to that particular genre. The application idea itself is not super important (suffice to say it does allow us to give a concrete implementation of Python, SQLite and Wasm for your benefit). The main idea of this article is to give you enough information to point out the advantages of running a Wasm-powered application on Fermyon Cloud instead of maintaining a traditional web server yourself or paying for other hosting alternatives.

The Money Side

Not to labor on the money side, but in relation to the calendar application idea, we can see that a quick web search shows how subscriptions to calendar services can cost users roughly $4.49 per month or $44.99 annually. Let’s briefly hypothesize a fictitious example: say we could get 1,000 users, each paying $45 annually. That would equate to an annual revenue stream of $ 45,000 … interesting!

As the owner of an application (that has end users) you can rest assured that when using Fermyon Cloud you are not paying for compute instances that sit around idly. We have solved the scale to zero problem to the point where each Wasm component is only running if there is an incoming request for it. Just for the record, below is a list of features that Fermyon Cloud provides at no cost.

Fermyon Cloud Pricing offers the following Starter tier; for $0/month:

  • Up to 5 applications
  • 100,000 request executions
  • Up to 5 custom domains
  • Up to 5 sub-domains
  • Up to 5 key-value data stores
  • 1 GB key-value storage
  • 1 GB SQLite database storage
  • 5 GB Bandwidth (egress)

Ok, so let’s roll up our sleeves and see how this all works. Please feel free to follow along; all of the following software is easily accessible and free to start using.

Installing Spin

If you haven’t already, please go ahead and install the latest version of Spin. The installer script and Homebrew installer automatically install Spin templates and Spin plugins; allowing you to move straight to the next section.

Upgrading Spin: If you have an older version of Spin, please see the Spin upgrade page of the developer documentation.

If you installed Spin from source, e.g. using make build or cargo (as opposed to using the install script or Homebrew installer) you will need to run the following commands as well:

# Fetch the latest Spin plugins from the spin-plugins repository
$ spin plugins update
# Install cloud plugin
$ spin plugin install cloud
# Install Python plugins and templates
$ spin plugins install py2wasm
$ spin templates install --git https://github.com/fermyon/spin-python-sdk --upgrade

Create Python SQLite Web Application

Please note, that the following embedded video “Wasm-powered web applications using Python and SQLite” is an accompanying resource (that walks through the same app creation process as this article).

First, we create a new application (passing in the http-py value via the -t option):

$ spin new -t http-py
Enter a name for your new application: public-facing-application
Description: An example public facing application
HTTP path: /...

We then configure our application to provision SQLite Database storage that persists across our Spin application’s invocations and updates (adding sqlite_databases = ["default"] at the component level):

[component.public-facing-application]
sqlite_databases = ["default"]

Note: You can use the Spin runtime configuration file to add and customize SQLite databases at the per-component level, but is outside of the scope of this article.

For our example, using the default built-in SQLite storage mechanism, we can now create our calendar application’s SQLite Database, using the following command:

$ spin cloud sqlite create calendar-database

This is enough to get us started and illustrates the basic idea. Now that the basic configuration has been performed, we use the spin build command to build the application:

$ spin build
Building component public-facing-application with `spin py2wasm app -o app.wasm`
Spin-compatible module built successfully
Finished building all Spin components

It makes sense to have a different data set for our local development environment and our public-facing production environment. Therefore, having a universal migration.sql file to store our reusable SQLite syntax makes sense, so let’s create one:

$ vi migration.sql

If you are following along with this article, please paste the contents of the code block below into the new migration.sql file. The syntax is the basis of our calendar application’s relational database:

CREATE TABLE users (
    user_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE
);

CREATE TABLE event_types (
    type_id INTEGER PRIMARY KEY,
    type_name TEXT NOT NULL
);

CREATE TABLE events (
    event_id INTEGER PRIMARY KEY,
    user_id INTEGER,
    type_id INTEGER,
    title TEXT NOT NULL,
    description TEXT,
    event_date DATE NOT NULL,
    start_time TIME,
    end_time TIME,
    FOREIGN KEY (user_id) REFERENCES users(user_id),
    FOREIGN KEY (type_id) REFERENCES event_types(type_id)
);

With this very first iteration of our app built, we can now go ahead and use the spin up command to run our SQLite (migration.sql file) using the @filename syntax as part of the --sqlite option:

$ spin up --sqlite @migration.sql
Logging component stdio to ".spin/logs/"

Serving http://127.0.0.1:3000
Available Routes:
  public-facing-application: http://127.0.0.1:3000 (wildcard)

Once the spin up command from above is executed, we will see a new file appear at public-facing-application/.spin/sqlite_db.db.

Creating Our Business Logic

The module for interacting with an SQLite Database via Python is defined here (an integral part of the spin-python-sdk). The great news is that the parameters we pass into the SQLite syntax are Python values (numbers, strings and lists), and Spin infers the underlying SQL type. To kick things off (write SQLite via Python) go ahead and paste the following code into the app.py file:

import json
from spin_http import Response
from spin_sqlite import sqlite_open_default

def handle_request(request):
    conn = sqlite_open_default()
    result = conn.execute("SELECT * FROM events WHERE event_id > (?);", [0])
    rows = result.rows()
    return Response(200,
                    {"content-type": "application/json"},
                    bytes(str(rows), "utf-8"))

After this new Python logic is saved in the app.py file, we can build and run our application in the following single command:

$ spin build --up
Logging component stdio to ".spin/logs/"
Storing default SQLite data to ".spin/sqlite_db.db"

Serving http://127.0.0.1:3000
Available Routes:
  public-facing-application: http://127.0.0.1:3000 (wildcard)

When you visit the http://127.0.0.1:3000 endpoint of your locally hosted application, you may realize that the application does not return any rows of data. That is because the database tables are still empty. Let’s go ahead and generate some test data, load it into the database tables, and rerun this application. We create a new file called populate-calendar.sql and then paste the following code into it:

INSERT INTO users (name, email) VALUES ("Tim McCallum", "tim@example.com");
INSERT INTO event_types (type_name) VALUES ("Presentation");
INSERT INTO events (user_id, type_id, title, description, event_date, start_time, end_time) 
VALUES ((SELECT user_id FROM users WHERE name = "Tim McCallum"),
        (SELECT type_id FROM event_types WHERE type_name = "Presentation"),
        "Tim Presenting", 
        "How to build a Python and SQLite Wasm application for the web", 
        "2023-12-05", 
        "18:00", 
        "19:00");

Then, load the data into the database via the spin up command again (this time passing in the new populate-calendar.sql file):

$ spin up --sqlite @populate-calendar.sql
Logging component stdio to ".spin/logs/"
Storing default SQLite data to ".spin/sqlite_db.db"

Serving http://127.0.0.1:3000
Available Routes:
  public-facing-application: http://127.0.0.1:3000 (wildcard)

If we visit localhost, we will see one row returned. The data we fetch from SQLite via Python returns a valid JSON object (the de facto standard for data interchange on the web):

[
    [
        1,
        1,
        1,
        "Tim Presenting",
        "How to build a Python and SQLite Wasm application for the web",
        "2023-12-05",
        "18:00",
        "19:00"
    ]
]

RESTful API Using Python

Let’s take a look at what a RESTful API would look like. This is just a trivial example, but it makes the point that we can take incoming requests at certain endpoints, access SQLite via Python and then return responses to the calling clients:

import os
import json
from spin_http import Response
from spin_sqlite import sqlite_open_default

def handle_request(request):
    # Make a connection
    conn = sqlite_open_default()
    # Add a new event using the POST verb in a secure HTTP request
    if request.method == 'POST':
        json_str = request.body.decode('utf-8')
        json_object = json.loads(json_str)
        # Add new user and type which automatically creates user_id and type_id
        add_event_pre = conn.execute('''INSERT INTO users (name, email) VALUES (?, ?);''', [json_object["name"], json_object["email"]]) 
        add_event_pre_2 = conn.execute('''INSERT INTO event_types (type_name) VALUES (?);''', [json_object["type_name"]])
        user_id = conn.execute('''SELECT user_id FROM users WHERE email = (?);''', [json_object["email"]])
        uid = user_id.rows()[0][0]
        type_id = conn.execute('''SELECT type_id FROM event_types WHERE type_name = (?);''', [json_object["type_name"]])
        tid = type_id.rows()[0][0]
        add_event = conn.execute('''INSERT INTO events (user_id, type_id, title, description, event_date, start_time, end_time) VALUES (?, ?, ?, ?, ?, ?, ?) ;''', [uid, tid, json_object["title"], json_object["description"], json_object["event_date"], json_object["start_time"], json_object["end_time"]])
        result_post = conn.execute('''SELECT * FROM events ORDER BY event_id DESC LIMIT (?);''', [1])
        rows = result_post.rows()
    # Respond with the event that the request asked about
    if request.method == 'GET':
        result_get = conn.execute('''SELECT * FROM events WHERE event_id = (?);''', [request.uri.lstrip('/events/')])
        rows = result_get.rows()
    return Response(200,
                {"content-type": "application/json"},
                bytes(json.dumps(rows),"utf-8"))

In addition to the above, we need to tweak our application’s manifest (the spin.toml file). Replacing the default route = "/..." with our new route = "/events/..." route:

[[trigger.http]]
route = "/events/..."

We now use spin build --up to build and run our application:

$ spin build --up

Building component public-facing-application with `spin py2wasm app -o app.wasm`
Spin-compatible module built successfully
Finished building all Spin components
Logging component stdio to ".spin/logs/"
Storing default SQLite data to ".spin/sqlite_db.db"

Serving http://127.0.0.1:3000
Available Routes:
  public-facing-application: http://127.0.0.1:3000/events (wildcard)

Then, test out the making of a request that uses the POST verb to add a new event:

$ curl -X POST "https://public-facing-application-ji9xzinh.fermyon.app/events" -H "Content-Type: application/json" -d '{"name": "Sally Slide-Show", "email": "sally-slide-show-2023@example.com", "type_name": "Presentation", "title": "Sally Slide-Show Presenting", "description": "How to build a Python and SQLite Wasm application for the web, part II", "event_date": "2023-12-05", "start_time": "19:00", "end_time": "20:00"}'

[[2, 2, 1, "Sally Slide-Show Presenting", "How to build a Python and SQLite Wasm application for the web, part II", "2023-12-05", "19:00", "20:00"]]

We can also test out the making of a request that uses the GET verb to obtain an event by event_id primary key that SQLite increments for us automatically:

$ curl -X GET "http://localhost:3000/events/2" -H "accept: application/json"

[[2, 2, 1, "Sally Slide-Show Presenting", "How to build a Python and SQLite Wasm application for the web, part II", "2023-12-05", "19:00", "20:00"]]

Front-End User Interface

There are almost too many calendar front-end User Interfaces (UIs) to choose from. For example, a quick search shows a page of 30 calendar interfaces. You can use React, Vue, JavaScript, Typescript, jQuery, or Angular to scaffold the front-end UI and make calls to the calendar application’s RESTful API endpoints.

Deploying to Fermyon Cloud

We deploy our application to Fermyon Cloud using the spin deploy command, as shown below:

spin deploy

We follow the prompts (in this case choosing to create a new database and link the app to it). Then once deployed we inspect the database using the spin cloud sqlite list command:

$ spin cloud sqlite list

+------------------------------------------------------+
| App                         Label     Database       |
+======================================================+
| public-facing-application   default   passionate-sun |
+------------------------------------------------------+

We mentioned a while back that we would be using a development dataset (for testing) and a production dataset (with real user data). To create the database’s tables we run the following command and pass in our migration.sql file (like we did above). This creates a matching schema (as per the same migration.sql file) for our production application:

$ spin cloud sqlite execute -d passionate-sun @migration.sql

Now we are free to go ahead and interact with the database tables, as per the following example:

$ curl -X POST "https://public-facing-application-ji9xzinh.fermyon.app/events" -H "Content-Type: application/json" -d '{"name": "John Smith", "email": "john-smith@example.com", "type_name": "Presentation", "title": "John Smith Presents", "description": "A Python and SQLite example", "event_date": "2023-12-05", "start_time": "12:00", "end_time": "13:00"}'

[[1, 1, 1, "John Smith Presents", "A Python and SQLite example", "2023-12-05", "12:00", "13:00"]]

$ curl -X GET "https://public-facing-application-ysamn653.fermyon.app/events/1" -H "accept: application/json"

[[1, 1, 1, "John Smith Presents", "A Python and SQLite example", "2023-12-05", "12:00", "13:00"]]

As you can see, we can concentrate solely on our business logic and data schema. The services that support our application are available and we can simply focus on our code! Even the transition from developing our application on localhost and deploying to Fermyon Cloud was seamless. We did not need to change a line of source code in order to move from development to production.

Custom Domains

In order to maintain a high degree of professionalism your web application needs to be aligned with your business branding and identity. As you can see above, every Fermyon Cloud application is initially deployed with a fermyon.app suffix. You can use our Custom Domains feature to present your Fermyon Cloud application, to your customers, using your own domain name.

One tangible example of using Fermyon Cloud to host a website using a custom domain is this blog articles domain, fermyon.com. That’s right, our website and developer documentation are examples of web applications, developed using Spin and are hosted, for your reading pleasure, on Fermyon Cloud. Our article on optimizing a Wasm-powered website for SEO covers what happens under the hood when users and web crawlers visit Fermyon.

Conclusion

I hope this has been enough of a dive into the technical side of things to get you excited about deploying your own Wasm-powered web applications. If you were only reading (not following along and coding with us) please consider dipping your toe in the water with our quickstart guide for Fermyon Cloud.

Thanks for reading. Please check out our GitHub repositories and visit us on Discord if you have any further questions. Our developer documentation can be found at developer.fermyon.com.

Thanks for reading.


🔥 Recommended Posts


Quickstart Your Serveless Apps with Spin

Get Started