demondehellis

Full-Stack Wizard, Tech Guru & Bash Evangelist.

← Back to blog

Backend Without a Server

  • #dev
  • #serverless
  • #flask
  • #firebase
  • #python

How do you build a web app without dragging a server around with it?

What do you even need a backend or a server for? Most brochure sites, landing pages, and corporate websites can be done with plain HTML. Upload it to a public bucket or GitHub Pages and just serve static files. A custom backend - and with it, a server - shows up when you actually need to interact with the web: leave a comment, upload a photo, log in, take a payment.

Most of those “features” boil down to a few basic operations:

  • write/read from a database
  • upload/download a file
  • process some data
  • authorize a user

The wise uncles at Google, Amazon, and the rest of the cloud priesthood already sell ready-made solutions for all this, and you can build pretty much anything on top of them.

Clouds and Servers

Clouds

A cloud database means you don’t have your own server with Mongo or MySQL on it, but you do have an API that lets you read and write data to a database hosted and maintained by, say, Google.

Cloud file storage means you don’t have a server with a disk full of files, but you do have an API that lets you upload and download files from a disk hosted and maintained by the same Google.

Cloud functions, lambdas, and similar creatures mean you don’t have a server running your app, but you can upload a little Python script - or whatever else you like - into that same Google cloud and run it from a URL.

Anyway, I think the idea is clear: the server technically exists, just not in your possession. Which also means all the server-related problems - security, scaling, patching, updates, and so on - technically exist too, just not in your possession either.

All of this is generally called “cloud infrastructure,” and the stack that works with it is called serverless.

What runs on the backend?

In my case: Flask. It’s lightweight, flexible, and barely asks any questions. You can write an API, render a page, use templates, or just serve markdown.

Flask example

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/api/hello")
def hello():
    return jsonify({"message": "Hello from Flask!"})

An alternative is FastAPI. If you need OpenAPI, validation, type hints, and speed, FastAPI can be a nice option. Slightly more complicated, but it gives you more control and generates documentation on its own.

Flask in Cloud Functions

Flask wraps into cloud functions very easily - I use Cloud Functions for Firebase. You drop the code into a folder and deploy it with one command.

from firebase_functions import https_fn
from flask import Flask

app = Flask(__name__)

@app.route('/') # This route belongs to Flask
def hello_world():
    return 'Hello from Firebase'

@https_fn.on_request() # And this handler belongs to Firebase Functions
def main(req: https_fn.Request) -> https_fn.Response:
    # Here we pass the request into Flask
    with app.request_context(req.environ):
        return app.full_dispatch_request()

Jinja - Flask’s template engine

Another nice Flask feature is the built-in Jinja templating engine. It makes it easy to split HTML into partials, reuse blocks, and inject variables.

<!-- layout.html -->
{% raw %}<html>
    <head>
        ...
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
</html>{% endraw %}

Use the layout in a page:

<!-- page.html -->
{% raw %}
{% extends "layout.html" %}

{% block content %}
    <h1>{{ title }}</h1>
    <p>{{ message }}</p>
{% endblock %}
{% endraw %}

Render the page:

from flask import render_template

@app.route('/page')
def page():
    return render_template('page.html', 
        title='Hello', 
        message='Welcome to Flask!'
    )

Flask + Flask-RESTX

If the API starts growing tentacles, it’s handy to add Flask-RESTX. It lets you describe resources, routes, and types pretty quickly.

from flask_restx import Api, Resource, fields, marshal_with

# Define a model
UserPublic = api.model('User', {
    'id': fields.Integer,
    'name': fields.String,
    'email': fields.String
})

@api.route('/user')
class UserResource(Resource):
    @marshal_with(UserPublic) # Serialize response using the model
    def get(self):
        # Return a dict or object
        return {
            'id': 1,
            'name': 'John Doe',
            'email': 'john@example.com',
            'password': 'secret123',  # This field won't make it into the response
            'internal_data': 'hidden'
        }

Useful things in Flask-RESTX:

  • Automatic Swagger/OpenAPI docs
  • Data validation via models and parsers
  • Response serialization - structured JSON output with field-level control
  • Namespaces for grouping endpoints
  • API versioning support

And pretty much anything else you might want for building a RESTful API.

Firestore

Firestore is Firebase’s cloud NoSQL database. It integrates easily with Flask and lets you store data as documents and collections.

from firebase_admin import firestore

@app.route('/api/articles', methods=['GET'])
def get_articles():
    # Connect to Firestore
    db = firestore.client() 
    
    # Get a reference to the "articles" collection
    articles_ref = db.collection('articles') 
    
    # Fetch document iterator
    docs = articles_ref.stream() 
    
    # Turn it into JSON
    return jsonify([doc.to_dict() for doc in docs]) 

Or, for example, writing data:

from firebase_admin import firestore

@app.route('/api/articles', methods=['POST'])
def create_article():
    db = firestore.client()
    articles_ref = db.collection('articles')
    new_doc_ref = articles_ref.add(request.json)
    
    return jsonify({"id": new_doc_ref.id}), 201

No schema required, data can be whatever. But you can use security rules to control access:

service cloud.firestore {
  match /databases/{database}/documents {
    
    match /articles/{articleId} {
      allow read, write: if request.auth != null;
    }
  }
}

There are also rules for data validation: type, size, required fields, regex checks, and so on.

Only Python?

Of course not. Firebase supports Node.js too, but I’m more comfortable with Python. Other cloud providers also offer Go, PHP, and plenty of other options.

Google also has Cloud Run - a service for running containers. You can package your app into Docker and run it in the cloud. In that case you can use literally anything that works inside a container. You can even run shell scripts or console apps if that’s your idea of fun. Unlike Cloud Functions, where code is triggered by an event and lives for a limited time, Cloud Run can keep your application running continuously like a regular server.

Why serverless?

I feel like we’re all headed there eventually. We just need one more breakthrough - maybe someone will ship a “Laravel for serverless,” and then we’ll all peacefully move in.

The advantages are obvious:

  • Cost. You only pay for what actually runs.
  • Highload. Scalability out of the box.
  • Infrastructure. No need to maintain, patch, or monitor servers.
  • Security. Providers take care of a lot of the security burden. If you’re not some elite devops wizard, your odds of messing up a self-managed server are usually much higher.

The downsides are mostly unusual architecture and vendor lock-in. And at this stage, the lack of high-level frameworks, CMSes, and similar conveniences.

My guess is that self-hosted servers will stick around only for very specific jobs - streaming, games, or anything that needs a permanent connection.