Articles

Building a SaaS product with Symfony (part 1/2)

This article is the first part of an ongoing two-parts article about how to build a SaaS product with Symfony.
Subscribe to my newsletter to be notified about part 2:


Usually, the articles I write and the talks I give are technical: they focus on a specific aspect of a technology I like, usually around Symfony, JavaScript or Docker.

Today however, I would like to take a slightly different path and talk to you about the role of technology in entrepreneurship, and more specifically, I would like to propose you ideas on how to use Symfony to build Software as a Service (SaaS) products.

Note that this article is by definition quite optionnated, and that's fine: I'm giving an opinion, not truth.


A SaaS product is a product that you buy and use online (no installation required) and is usually subscription-based (you pay monthly/yearly fee to use it).

This model has big advantages for a company:

  • by providing a regular and predictible stream of revenue, subscription models allow companies to plan (and thus to invest) more easily
  • by having a single shared codebase, SaaS products provide a focus, increasing the team productivity

  • by sharing features across customers, SaaS tools ensure clients that they are going to profit from the bug fixes, new features and maintenance of other customers for free (this is why I usually think of SaaS as the economy of scale of IT services)

These advantages are the main reasons why this model is so used nowadays. It's a powerful driver for company growth.

Let's discuss some ideas on how to start your own SaaS product with Symfony.


Start small

When you start a SaaS product or company, unless you already have a large audience of potential customers, you usually won't have much traffic and usage. That's normal.

At the same time you will have to deploy often to adapt and find your market fit (the spot in the market where your product addresses a need better than competitors).

The best way to be able to deploy often when you don't have much traffic and resources is to start small. Do not build everything at the beginning: only build what you need, when you need it.

In practice, there are several ways to implement this:


Use a single server

You don’t need multiple servers. Adding servers to an infrastructure exponentially increase its complexity: it will be more difficult/expensive to deploy, to monitor, to analyze logs, etc. You shouldn't add servers unless you are required to.

A single server is usually more than enough to power a small SaaS product: you won't have much traffic/customers at the beginning but having a single server will dramatically ease deployments.

You should however not store anything important on the server itself: instead, rely on managed persistence platforms.


Rely on managed persistence platforms

One of the most important technical process you need to have in any company is backup and data loss prevention. Losing your customers data may well be the end of your company, preventing this risk is thus extremely important.

Having a single server could be considered a bit risky on this level: that's why you should probably store all important data on a managed platform instead.

Nowadays, almost all hosting providers propose an Object Storage system (to store files) and a managed database (PostgreSQL, MySQL, ...). By using these two, you will be able to:

  • ensure your data and files won't be lost due to an infrastructure failure: they are managed and backed-up automatically by your hosting provider
  • scale almost infinitely: by being managed and outside of your fleet of servers, these platforms will allow you to grow as much as you can

PostgreSQL is especially good because it is able to manage both data (as a database) and queues (with Symfony Messenger). A managed PostgreSQL server is a 2-in-1 platform for the cost and complexity of only the database.

To externalize files, data and queues to managed platforms, you will need to configure three things:

    • To configure PostgreSQL as your database, use the DATABASE_URL environment variable: DATABASE_URL=postgres://main:main@database:5432/main?sslmode=disable&charset=utf8

    • To use PostgreSQL as your queuing platform, configure Symfony Messenger to use the Doctrine connection to your database. Symfony will automatically detects PostgreSQL and use the appropriate configuration.

    • Finally, to use the Object Storage of your hosting provider, I recommend you to use the Flysystem library (and its bundle). It will allow you to configure multiple storage locations for your files, and choosing which one to use at runtime (in dev/test/production):

    # config/packages/flysystem.yaml
    flysystem:
        storages:
            cdn.storage.memory:
                adapter: 'memory'
                
            cdn.storage.aws:
                adapter: 'asyncaws'
                options:
                    client: 'aws_s3_client_sdk'
                    bucket: '%env(AWS_BUCKET)%'
    
            cdn.storage.local:
                adapter: 'local'
                options:
                    directory: '%kernel.project_dir%/var/storage/cdn'
    
            cdn.storage:
                adapter: 'lazy'
                options:
                    source: '%env(APP_CDN_STORAGE)%'
    # .env
    APP_CDN_STORAGE=cdn.storage.local
    
    # .env.test
    APP_CDN_STORAGE=cdn.storage.memory
    
    # Production env
    APP_CDN_STORAGE=cdn.storage.aws


    Leverage simple, standard technologies

    You shouldn't try to solve issues you don't have yet. There are trends in IT and that's normal, but you probably shouldn't listen to them too much: by nature, new technologies emerge when someone stumbles upon a problem and tries to solve it better than the existing solutions. But the thing is, you probably won't encounter new technological problems while building your company or product.

    Using Kubernetes should be reserved to the time you will need the features it provides. The same goes for any tool in your stack: adding a tool takes time now and in the future. Keep things simple and standard as long as possible: it will help you dramatically over time.

    A few examples of simple standard technologies:

    • PHP is used everywhere, by everyone. It's not trendy to say it, I know, but that's the truth: almost every single company in the world has a bit of PHP somewhere (for their marketing website, their internal accounting software, their ticketing system, their project management tool, ...). Using PHP is the safest bet: it won't disappear in the coming decade and it'll allow you to hire more easily than many other programming languages.
    • PHP-FPM and nginx work very well together: in a few lines of configuration, you can set up a complete Web server, with URL rewriting, assets management, low memory footprint and horizontal scalability. For instance:

    server {
        listen 80;
    
        # Serve all requests (no domain filtering)
        server_name _;
    
        # Directory of the PHP project
        root /app/public;
        index index.php;
    
        # Browser security headers
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
    
        # URL rewriting (look for an existing file, otherwise transfer to index.php)
        location / {
            try_files $uri /index.php$is_args$args;
        }
    
        # PHP-FPM configuration
        location ~ \.php(/|$) {
            internal;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_split_path_info ^(.+\.php)(/.*)$;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param HTTPS off;
        }
    }


    • Deploying with a small script, Git and SSH is totally fine at the beginning: you could have a small bin/deploy.sh file in your project to rebuild cache, launch Doctrine migrations, .. and use it to deploy with Git and SSH:
    ssh myserver.com "cd /path/to/app && git pull && bin/deploy.sh"


    Use a Content Delivery Network

    In addition to your standard single server and your managed persistence platforms, a common need you will definitely encounter buulding your app is how to serve assets (CSS files, JavaScript files, images, ...). Serving them from your server directly is possible but you should probably use a Content Delivery Network instead.

    A CDN (Content Delivery Network) is a network of servers distributed across the globe that are able to serve static files extremely fast by caching them close to your visitors. It means that a visitor from France will receive the assets from Paris and a visitor from New York will receive it from New York City.

    I personally use Cloudflare on all my websites: it's free at the start (and cheap after) and it provides very useful features:

    • It distributes your assets (it acts as a CDN), decreasing the amount of traffic your server receives and increasing the speed of your website.
    • It prevents Deny of Service attacks: Cloudflare sits between your visitors and your webserver so that your server IP adress is never exposed online. This allows Cloudflare to absorb Deny of Service attacks completely, avoiding your server to crash.

    • It handles the HTTPS connection for you: as your visitors uses Cloudflare to access your website, Cloudflare is able to handle the HTTPS connection, including the automatic free renewal of certificates.


    Build everything incrementally

    At some point however, you will need a second server or you will need more advanced features for your queing mechanism/object storage/... Then at this time, it's completely fine to change your infrastructure: applying a change to fix a problem is the only good time to apply a change.

    When you will need to scale (ie. have a second server for instance), you should have a look at Redis: it is an amazing tool to synchronize your Symfony application cache and sessions across multiple servers.

    In the same vein, RabbitMQ is a very powerful queuing platform able to handle huge amount of messages and providing more advanced features than the simple PostgreSQL approach. Have a look if you stumble into problems with PostgreSQL.


    Use SymfonyCloud!

    And finally: use SymfonyCloud. It embeds automatically everything I just mentionned (managed database and filesystem, scaling, easy deployment, CDN, ...) in a single unified tool.


    This article is the first part of an ongoing two-parts article about how to build a SaaS product with Symfony. 

    Upcoming

    • how to plan for success
    • what to do early to avoid issues

    • what database structure you will very likely need

    • how you should rely on other tools

    • how and when to automate

    Subscribe to my newsletter to be notified about part 2:

    Introducing the league/flysystem-bundle
    Apr 18, 2019
    On April 6th and 7th 2019, I was honored to take part in the EU FOSSA Symfony Hackathon 2019. It the occasion for me to work on an idea I had: implementing a modern Flysystem integration for Symfony.
    Using Symfony Security voters to check user permissions with ease
    Jul 31, 2018
    Checking users permissions is a crucial part of many web projects and a single mistake can be devastating. That’s why Symfony Security voters are so great.
    Tips for a reliable and fast test suite with Symfony and Doctrine
    Jul 24, 2018
    A great feature of Symfony is it’s organization around HTTP: its kernel “handles” HTTP requests and “returns” HTTP responses. This pattern brings tons of advantages, including ease of testing.