Building a Laravel SAAS, what I’ve learned.

Ideally I’ve been spending the last few days thinking about how I could’ve done things better from a coding point of view, when I’ve build a personal SAAS Project, Business Consulting which plain simple would be an easy invoicing and document redaction application dedicated for Romanian Business Owners or future owners, there’s still a lot of work involved, and I’m primarily focusing on bringing more features, rather than making a good presentation.

0. The premise

The application is done using Laravel 5, an awesome framework that allows me to code the way I like, ideally my first “love” was Zend Framework, which version 1 helped me understand a lot when it comes to PHP Design Patterns, and Application Architecture.
I haven’t coded using MVC Patterns for almost a year, as I’ve primarily focused on WordPress Plugin Development.
We’ve wanted to have an MVP as fast as possible to make sure we get something that people can use, and gather as much feedback as fast as possible.

1. Duplication of Security Checks

I’m following some specific URL Patterns, such as :

Notice the flaw ? Ideally I’m doing the access validation everywhere in the code, that means around 45 – 50 lines that do exactly the same thing, check if a specific user has access to a company. ( Right Now we allow only one user to have access to it, think how much refactoring will be needed and places to change this when I’ll add a multiple users layer specifically for Big Companies.

Now let’s dig deeper, if my URL Patterns would’ve been like this, it would be a totally different story :

What just happened ? The company ID is always the first parameter.

Right now my routes are something similar to this :

If I would’ve followed the pattern, and always have the company ID First, it would be much more easier, it would be something like this :

the following code was not tested, and ideally it’s just an example to prove a point

What would be the next step ?

Creating another Middleware called company_access, that would grab the first param, the Company ID and make sure that the user has access to it, what would happen ?
45 – 50 lines of code that do the same thing would be in one single place. And ideally only the general access code should be here.

2. No Easy way to change the URLs

Ideally I didn’t take the time to define an alias for every route, this could’ve saved me a lot of time, considering that IF I’m going to re-make the entire way the application routing works, I’ll manually have to go trough and replace everything, cool, right ?

If I would’ve spent the right time at the start, it would’ve saved me much more time right now
Back to the point, I didn’t expect the application to grow so fast, so easily. We have around 15 entities, from which 7 are administrated by companies on different pages, such as Invoice Status, Invoice Delivery Method, Receipts, Invoices, Products / Services, Client List, Payment Methods, and a few generic pages such as History, Primary Settings and Company Information Administrations. And these are just things that work around companies directly.

We’ve planning to bring even more and more features, and I’m 100% sure, that if I don’t change this right now, the amount of work needed to do this properly in the future would be as twice as much.

I’m honestly really happy that I’ve learned my lesson about this, and in the future I’ll be much more careful about these things, in WordPress I’m usually defining some variables for specific requests that use GET or POST, because I’ve forgot about things as important as URL naming conventions, because let’s be serious, in WordPress, we don’t really have to care about these things, as we want to leave all the heavy lifting into custom post types, posts or shortcodes.

3. Constant DB Changes

The project went a totally different road than me and my partner initially planned to go, bringing more features and unexpected things to the table. I’ve spent a lot of time changing, and revising the way the DB works, and I’m really glad that I’ve kept it clean, every time there was a logic bummer in the Database I would resolve it ASAP, always making sure I take all the things that would come up into account.

Now I’m wondering why the other part wasn’t as planned as this one, the answer is really simple. Experience. 2 years ago I’ve spent countless hours refactoring a database structure after working on a specific one for a few days, I’ve spent around 16 straight hours to re-think it, re-design it, and refactor the code to match it. I’ll never do that again, so I plan to staying ahead of things and making sure I don’t face unexpected problems.

Small Thing I appreciate in WordPress about this

In WordPress this isn’t usually an issue, if the code is right
– you can just change the way it’s stored in the meta
– or quickly define a taxonomy and store things differently.
– or maybe, if you’ve needed to use a custom table, actually change it and adapt your models.

4. The Language Files

Laravel uses language files, ideally you’ll have to have an file & alias ready for each string used in the application if you want to easily translate it or change it in the future.
This is so much different from WordPress, because there you would just write the text, namespace it properly, and then you could just generate the .mo and .po files, and don’t even remember a naming convention !

For example some of my log strings / messages.

4.1 My approach to logging actions / messages

Storing raw notification strings directly would be a bad approach from an UX point of view and coding flexibility, as a performance option, it’s much better, but I usually tend to ignore performance over flexibility & agile development, because caching things makes them even faster.
These would change or be affected by the extra language options in the future, ideally I’ve wanted to store them properly in the database, and make sure that’s not a problem.

4.1.1 The Database Structure

acte-express-db-structure

4.1.2 I’ve created an function for this model, to make things easier for me.

Using the following model is pretty easy, I’ll just use the log function, to make sure I can actually centralize future cases where I might do other things before logging or etc.

4.1.3 I’ve written a very simple snippet to display the logs.

This is a blade templating engine code, ideally it will display the logs paginated.

The reason why I’m using {!! !!} instead of {{ }} ( which means no sanitization ) for logs is because for certain parts I’m planning to add links, ideally this is a totally safe approach, because I’m sanitizing all the entries, in case this will prove problematic in the future, I can always sanitize things during the CompanyLog::log function, because I’m pretty much having the same fields in many cases, such as :name, :number, :id

5. To be continued.

Thanks for reading and I hope you’ve enjoyed this article, feel free to email me or leave a comment if you have any questions.

  • Anas Ahmar

    WOW! amazing article.
    Thanks a lot for sharing this as I’m starting up a SAAS business. But I have a question.

    I’m implementing the scheme of one database shared across all tenants using a unique identifier in almost all database tables, so the ids of every table is auto incremented and appear in the URLs to the tenant, what I’m thinking of is to implement Hashids PHP library to encode and decode the ids on the fly (on each post and get request), so do you think this approach is appropriate for such problem?

    One more thing make me stumble upon is, how would a developer add additional auto increment ids column counting from 1 onwards for every single tenant? like in your case scenario invoice with an ID of 1 and another once with an ID of 1 but for another tenant.

    So I thought this task should be leveraged to a database trigger to handle it on every new insert call. What do you think of this solution?

    • Hey there,

      The best option would be to have 2 tables, and when a record is added in table one, another is added in table 2, that was, the ids will always be properly generated, just modify the Model of the Primary Table on the save hook, to create a connection.

      For example : product ( id )
      product_data ( id, product_id )

      When a product is added, on save, it verifies if there’s an product_data for the product_id, if not, it adds a new one & updates the information if it’s required.

      That’s how I would do it, but it all depends on the case exactly, how much data you’re processing and how important is the ID consistency, if you just need an extra param to connect to the table, apart from the ID, you could just make an uniqid with a prefix and the second param as true.

      • Anas Ahmar

        How would your proposed solution be appropriate in case of multi tenants. If your tenant needs a serial number starting from 1 for his e.g. receipts.

        • you’re making a SaaS that sends emails ?

          • Anas Ahmar

            nope, actually its like a CRM ERP module