alihamzahq started a new conversation+100 XP
1d ago
Hi all,
I’ve been working on an open-source multi-tenant SaaS starter kit and finally got it to a point where I’m comfortable sharing it publicly.
It’s built with Laravel 12, React 19, Inertia.js, and Stancl/Tenancy, using a database-per-tenant approach.
Repo:
https://github.com/alihamzahq/laravel-multi-tenant-saas-starter
The main reason I built it is that most multi-tenant Laravel resources I found were either commercial products or isolated tutorials. I wanted something free and MIT-licensed that shows a more complete SaaS flow, including:
- Central admin panel
- Tenant impersonation using signed URLs
- Tenant-level RBAC
- REST API with Laravel Sanctum
- Subdomain-based tenant routing
- Database-per-tenant isolation
I’d especially appreciate feedback on:
- The tenant impersonation flow
Is the signed URL approach reasonable, or is there a more secure/idiomatic pattern?
- The RBAC layer
Particularly how permissions should behave across the central app and tenant apps.
- Project structure and naming
Anything that feels confusing, over-engineered, or not very Laravel-ish.
Demo credentials are included in the README, and the project runs locally with a basic subdomain setup.
Happy to discuss any part of the architecture.
Thanks for taking a look.
alihamzahq wrote a reply+100 XP
1d ago
If you are truly okay with losing the entire log table, I would use TRUNCATE, not a chunked delete.
For this case, chunked deletes are mainly useful when you need to keep some rows, avoid long locks, or delete gradually while the table is still being used. But if the table is just a disposable audit/log table and nothing references it, TRUNCATE TABLE change_logs; is the cleaner option.
A few things I’d still do first:
-- take a backup if there is any chance you may need the data
CREATE TABLE change_logs_backup LIKE change_logs;
-- optional, only if you want a sample or full copy before wiping
-- INSERT INTO change_logs_backup SELECT * FROM change_logs;
TRUNCATE TABLE change_logs;
Important caveats:
TRUNCATE is DDL-like behavior in MariaDB/MySQL. It effectively drops and recreates the table, is much faster than DELETE, resets auto-increment, and cannot be rolled back once committed. MariaDB also documents that TRUNCATE TABLE causes an implicit commit.
So I would run it during a quiet/off-hours window, after confirming:
SHOW CREATE TABLE change_logs;
Check that there are no foreign keys referencing it, no active app code writing to it, and no long-running queries holding locks on it.
In short: if you need to preserve some rows, delete in chunks. If you genuinely want the table empty, use TRUNCATE.
alihamzahq wrote a reply+100 XP
1d ago
I usually prefer a feature/domain-based structure, with small layer-based folders inside each domain.
Instead of one global Services, Actions, or Contracts folder, I like keeping related code together:
app/
Domain/
Offers/
Actions/
States/
Contracts/
Listings/
Actions/
Services/
Contracts/
Billing/
Actions/
Services/
Contracts/
Http/
Controllers/
Offers/
Listings/
Billing/
Requests/
Offers/
Listings/
Billing/
That gives a good balance: the business logic is grouped by feature, but each feature still has clear separation between actions, services, contracts, states, etc.
A good public example of this style is:
github.com/alihamzahq/pulsepass-api
It follows a similar domain-style flow, with business logic grouped under app/Domain/ and HTTP concerns kept separately under app/Http/.
My general rule is: structure the app first by business capability, then by technical role inside that capability.