Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mijalchev's avatar

Multitenant architecture managed over Vapor?

Hi,

I have carefully watched T. Otwell's presentation about Vapor on Laracon. His presentation was very good and inspired me to think about Vapor as a possible solution for hosting our next project.

First I want to point out that I still do not have a Vapor account and everything that I know about it is from the mentioned presentation.

Vapor is a new product but I hope someone that is using it, will be able to comment to this in order to see if our scenario is achievable with the current Vapor features.

The scenario is to build a multi-tenant SAAS application with Laravel. The application will be driven by one code base but each tenant will have a separate storage space and separate database.

I assume that the separate storage space can be solved by adding each tenant's data in a separate folder on S3.

But how about the creation of separate databases. How to manage the environment varialbles for connection to each specific tenant database? How to automate the process of creating and deleting a database when a new tenant is introduced or a tenant need to be removed? Is it Vapor CLI that can be used somehow and how to be called from external application that would probably manage tenant's accounts?

The next question is what if we have the front-end as a completely decoupled application developed fully independently and need to be deployed separately. How should it be deployed?

Thanks in advance.

0 likes
12 replies
fylzero's avatar

@mijalchev Creation of each database is something fairly easy to build out... just look into having a controller do this when you create each tenant.

As far as multi-tenancy / multi-database... I came up with a really clean / simple solution using a custom config file.

In my example I am using states as the subdomains... so like https://iowa.example.com

My approach was to create a file called /config/connection.php...

<?php

$domain = $_SERVER['HTTP_HOST'];
$domain_parts = explode('.', $domain);
$subdomain = $domain_parts[0];

if ($subdomain == 'iowa') {
    config()->set('database.default', 'iowa');
}

if ($subdomain == 'nebraska') {
    config()->set('database.default', 'nebraska');
}

return ['subdomain' => $subdomain];

Setup multiple databases in my env...

# Database: Nebraska
NEBRASKA_DB_CONNECTION=mysql
NEBRASKA_DB_HOST=127.0.0.1
NEBRASKA_DB_PORT=3306
NEBRASKA_DB_DATABASE=nebraska_db
NEBRASKA_DB_USERNAME=root
NEBRASKA_DB_PASSWORD=

# Database: Iowa
IOWA_DB_CONNECTION=mysql
IOWA_DB_HOST=127.0.0.1
IOWA_DB_PORT=3306
IOWA_DB_DATABASE=iowa_db
IOWA_DB_USERNAME=root
IOWA_DB_PASSWORD=

Then specify the databases in /config/database.php...

'iowa' => [
    'driver' => 'mysql',
    'url' => env('IOWA_DATABASE_URL'),
    'host' => env('IOWA_DB_HOST', '127.0.0.1'),
    'port' => env('IOWA_DB_PORT', '3306'),
    'database' => env('IOWA_DB_DATABASE', 'forge'),
    'username' => env('IOWA_DB_USERNAME', 'forge'),
    'password' => env('IOWA_DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],

'nebraska' => [
    'driver' => 'mysql',
    'url' => env('NEBRASKA_DATABASE_URL'),
    'host' => env('NEBRASKA_DB_HOST', '127.0.0.1'),
    'port' => env('NEBRASKA_DB_PORT', '3306'),
    'database' => env('NEBRASKA_DB_DATABASE', 'forge'),
    'username' => env('NEBRASKA_DB_USERNAME', 'forge'),
    'password' => env('NEBRASKA_DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],

Now my databases are set manually in the .env and /config/database.php but you could simply store the database config to a table, and just pop new users in there when they are created in the app... then in your /config/database.php make a data call above the return, store all the configs to an array, then pass that into the config dynamically.

So in /config/database.php...

<?php

use Illuminate\Support\Str;

$multitenant_dbs = DB::table('tenant_db_configs');

return [

    ........

    'connections' => [
        $multitenant_dbs
    ]
    

Something to that effect. This leaves a bit of homework for you... but as far as multi-tenant/separate db, I really was happy with this implementation. You don't have to do anything fancy with subdomains in routes or anything... it just detects the subdomain and switches the database accordingly. Also, since you're returning the subdomain in a config... you have access to that everywhere in the app.

config('connection.subdomain')
1 like
mijalchev's avatar

@fylzero This is really good peace of advice and is definitely worth of attention. I completely understand your point from the aspect of the code architecture and it will certainly work in a manual setup. But considering that we want to automate the process I was mostly concerned how much Vapor can support automation. Let assume that we have all mentioned above implemented and we trigger creation of a new tenant in our SaaS structure and a database for that tenant does not exists at all. I was wandering what is the way and if it possible in Vapor to somehow create the database instance itself automatically as a part of a process.

fylzero's avatar

@mijalchev lol @ me realizing the one thing I said would be easy was the actual problem you were trying to solve and is made more complex by Vapor / Lambda. Oops! :)

I would investigate if Lambda supports creating a database via command line... then see if you can create a console command in Laravel to execute that. I don't know off the top of my head, but that's where I'd start digging.

I haven't used Vapor yet, but have done a lot of reading on it and plan to deploy an app there soon.

1 like
fylzero's avatar

@mijalchev One other thing I'll say, after creating several projects using the multi-database approach... I would argue that single-database has more advantages down the road. I think, starting out, I always thought multi-db was "safer" in case I needed to do a data export/backup for a single client or I worried about not guarding data correctly... truth is, these concerns are easily mitigated after understanding how to scope data correctly. I'm just throwing out there that the multi-db approach has more limitations than you think, especially as you scale.

Seems counter-intuitive but if you haven't considered single-db, I would check it out. I know this also doesn't really answer your question but I've done a deep-dive into both approaches... I only use multi-db now to support legacy transitions to single-db. After I get my legacy platforms converted, I'll probably never use multi-db again.

...and believe me, I would love to hear a counter-argument to this. I'm always curious what people think about the pros and cons of each approach.

I would say that if Vapor doesn't easily support this, it should serve as a flag that it isn't necessarily the best pattern to scale.

1 like
fylzero's avatar

@mijalchev ...and to try to answer your separate question. If the front end is decoupled and doesn't need to be on the same domain... just deploy as separate apps?

1 like
mijalchev's avatar

Hi @devforall . Thanks for the links. If multiple second level domains are not supported like it is stated in the discussion for issue #74 than it can be a direct deal breaker for us. :-)

mijalchev's avatar

@fylzero Thanks for the answers connected to my original question. I will also continue to investigate by myself to see what is possible to do regarding the automated creation of the databases. According to what I was I able to understand so far, Lambda is not the only option to use for databases over Vapor. It is also possible to use standard AWS tiers with fixed resources. Regarding your comments around single vs. multiple databases... I think it is not possible to generalize the things. Each project is different story. ;-)

1 like
fylzero's avatar

@mijalchev Usually I would agree with you about not generalizing, but in this case after doing much research... I am inclined to say, declaratively, that I feel single database multi-tenancy is just a better pattern. It's less architecture and config to juggle. Frankly, programming multi-tenancy around owner ids is not as complex as it seems when you understand how to scope/guard your data calls. Again, I'd love to hear the pros of using multi-db in a project. I just don't see many. To me multi-db seems easier at first but locks you into a bad structure as you scale. Just my opinion.

Good luck!

1 like
samuelstancl's avatar
Level 8

Hi, developer of https://github.com/stancl/tenancy here.

Vapor is currently not ideal for multi-tenancy exactly because of this:

If multiple second level domains are not supported like it is stated in the discussion for issue #74 than it can be a direct deal breaker for us

They don't support multiple domains and they don't plan to.

Regarding database creation, if you look at my package, I create databases using a CREATE DATABASE query. This will work even on Vapor. A single database in Vapor is not actually a database, it's a managed MySQL/PostgreSQL/... instance that can have multiple databases inside, just like MySQL on your local computer.

@fylzero Using separate domains & databases solves a lot of pains. You don't have to manually scope every single thing. You don't have to care about sessions. Things just work. And especially if you use a package like mine, there is a close to zero implementation cost. You can write your application and implement tenancy later without having to rewrite your codebase.

2 likes
fylzero's avatar

@samuelstancl That is fair... I, personally, would just re-word that as "Using separate domains & databases solves a lot of pains... initially." ...and that is 100% just my opinion, definitely not a hard fact.

For me, as my company grew, multi-database became more difficult to scale. Namely because of database allotment limits on the servers we've looked into. I'm not sure how much of a real issue that is. We just sort of decided that scoping things to a correlated id wasn't as painful as is sounded. Once we changed over, it made me wish we'd done it that way all along.

Right tool for the right job, I suppose. I appreciate the perspective though.

1 like
samuelstancl's avatar

Sure. Different solutions come with different downsides. The single/multi-db choice depends both on the app and the problems you prefer dealing with.

For me, as my company grew, multi-database became more difficult to scale. Namely because of database allotment limits on the servers we've looked into.

I haven't dealt with that issue so I can't say much about that. But conversely, using multi-DB tenancy makes scaling easier in the sense that you (in 99.999999% of cases) don't need to deal with sharding at all. And sharding can be an enormous pain.

Also with my package you could use different DB servers for different tenants (or groups of tenants). So maybe after one server has enough databases, you could change the config so that new tenants use a different server. Or you could use multiple servers and assign databases depending on the region of the tenant.

2 likes

Please or to participate in this conversation.