Starting a Business with Laravel Spark

Share this article

I am really excited about Laravel Spark. By the time you read this, there will probably be a multitude of posts explaining how you can set it up. That’s not as interesting to me as the journey I’m about to take in creating an actual business with Spark!

The idea is simple. I have created a Pagekit module which you can use to back up and restore site data. The module makes it easy to store and download these backups, and restore them on different servers.

The trouble is, getting those backup files to the remote server takes time and a bit of hassle. I have often wanted a way to quickly and painlessly transfer this application state from one server to another, and make automated offsite backups. So I’m going to set that up for myself, and perhaps others will find it useful enough to pay for it.

Spark splash screen

Getting Started

I’m using Stripe, and intend to have a single plan with no trial. The setup for this is quite easy, but I’ve made a note of the plan ID. I’ll need that to set the plan up in Spark…

Stripe welcome screen

Next, I reset my secret and public Stripe keys and update to the latest API (through the same screen, https://dashboard.stripe.com/account/apikeys).

I forgot that the settings in .env do not automatically reload while the Laravel development server is running, so I was getting needlessly frustrated at the keys which wouldn’t seem to update.

Spark has a few expected registration/profile fields, but I want to add a few more. I would like to ask users if they would like automatic backups and I’d also like to collect their billing address, so I can show it on their invoice. First I’ll have to create a migration for the new field:

php artisan make:migration add_should_backup_field

To do this, we can add the column (making sure to remove it if the migrations are rolled back):

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddShouldBackupField extends Migration
{
    public function up()
    {
        Schema::table("users", function (Blueprint $table) {
            $table->boolean("should_backup");
        });
    }

    public function down()
    {
        Schema::table("users", function (Blueprint $table) {
            $table->dropColumn("should_backup");
        });
    }
}

Collecting billing addresses is as easy as a single method call, in app/Providers/SparkServiceProvider.php:

/**
 * @inheritdoc
 */
public function booted()
{
    Spark::useStripe();

    Spark::collectBillingAddress();

    Spark::plan("[Stripe plan name]", "[Stripe plan ID]")
        ->price(4.99)
        ->features([
            "Backups"
        ]);
}

This adds the billing address fields to the registration form:

Billing Address Field Added

Don’t forget to customize the rest of SparkServiceProvider.php to suit your needs, if you’re following along.

Storing Backups

The whole point of this app is to give content authors the ability to upload, list, and download their backups. Next I’ll create a Backup model:

php artisan make:migration create_backups_table
php artisan make:model Backups

The backups table needs a few fields:

  1. The name of each backup (perhaps containing the site name, the date and whether it includes database and upload data).
  2. The uploaded backup file.
  3. The size of each backup.
  4. A field to link the backup to the user that created it.

This is what the customized migration file looks like:

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;

class CreateBackupsTable extends Migration
{
    public function up()
    {
        Schema::create("backups", function (Blueprint $table) {
            $table->increments("id");
            $table->string("name");
            $table->string("file");
            $table->integer("size");
            $table->integer("user_id");
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists("backups");
    }
}

Given this database structure, I want to provide a set of JSON endpoints through which users can view and manipulate backups. Again, we can use the make command to get going:

php artisan make:controller BackupsController --resource

Spark is specifically designed to be a good starting-point for authenticated API creation. When you log in with a Spark account, you can go to a section in which you can create API tokens:

With this token, and having created the resource controller, I can make the index method echo some debug text. Then I can make curl requests to it:

curl -XGET http://localhost:8000/api/backup

For this to work, I need to define an API route (in app/Http/api.php):

Route::group([
    "prefix" => "api",
    "middleware" => "auth:api"
], function () {
    Route::resource("backup", "BackupsController");
});

Since I haven’t provided the api_token GET parameter, the request isn’t correctly authenticated, and Spark will try to redirect me to the login page. If I’m logged in at the time, I’m redirected back to the dashboard screen.

If I define the api_token parameter, I see the debug text:

I’ve opted for a simple upload mechanism, which does very little validation on the files (while I get the prototype working). I’ll want to harden this up later with some validation, but for now I have:

public function store(Request $request)
{
    $file = $request->file("file");
    $name = $request->get("name");

    if (!$file || !$name) {
        return [
            "status" => "error",
            "error" => "file or name missing",
        ];
    }

    $fileExtension = "." . $file->getClientOriginalExtension();
    $fileName = str_slug(time() . " " . $name) . $fileExtension;

    try {
        $file->move(storage_path("backups"), $fileName);
    } catch (Exception $exception) {
        return [
            "status" => "error",
            "error" => "could not store file",
        ];
    }

    $backup = Backup::create([
        "name" => $name,
        "file" => $fileName,
        "size" => $file->getClientSize(),
        "user_id" => Auth::user()->id,
    ]);

    return $backup;
}

I can test this by creating an empty backup file and then submitting it to the store action:

touch backup.zip
curl
    -X POST
    -F name="My First Backup"
    -F file=@backup.zip
    "http://localhost:8000/api/backup?api_token=[my token]"

I get back something resembling:

{
    "name": "My First Backup",
    "file": "1461802753-my-first-backup.zip",
    "size": 0,
    "user_id": 1,
    "updated_at": "2016-04-28 00:19:13",
    "created_at": "2016-04-28 00:19:13",
    "id": 8
}

Notice how the file is named differently to avoid collisions? It’s actually named after the backup’s name, not the original file name, and I’ve added the timestamp so it’s possible to upload similarly-named backups without the names clashing.

There’s still a race-condition for when users upload backups with exactly the same name in the same second. The risk of that is less, and I’m prepared to deal with it when it becomes a problem.

Now I can customize the index action to return more useful information:

public function index()
{
    return [
        "status" => "ok",
        "data" => Auth::user()->backups,
    ];
}

I’ve also had to create this relationship between users and backups by adding the backups method to the User model:

public function backups()
{
    return $this->hasMany(Backup::class);
}

Downloading Backups

I’m uploading the backups to a non-public folder for a reason. I don’t want users to be able to guess (no matter how slim the chance that they will guess correctly) how to access the backups of other users.

So I have to create a special downloads endpoint. I can, however, piggyback off the show action:

public function show($id)
{
    $backup = Auth::user()->backups()->findOrFail($id);
    $path = storage_path("backups/". $backup->file);

    return response()->download($path, $backup->file);
}

Because of how the query is scoped, users will only ever be allowed to download backups linked to their account, and the backups are not publicly accessible outside of this one route.

Conclusion

In an incredibly short space of time, I’ve managed to use Spark to create a subscription-based hosting platform, with an authenticated API for storing and downloading backup files.

There’s a lot more work to do, from theming the application to adding more security to implementing storage limits and backup deletion. Still, I’m encouraged by the start I’ve made, and I may revisit this application in future tutorials, as I learn more about Spark!

Do you have any questions about this code, or comments about what you’d do differently? Let us know in the comments!

Frequently Asked Questions about Starting a Business with Laravel Spark

What are the key features of Laravel Spark?

Laravel Spark is a powerful tool for developers looking to build a subscription-based business. It provides a robust set of features out of the box, including authentication, password reset, team billing, invoices, user impersonation, and more. It also integrates seamlessly with Stripe and Braintree for payment processing. This allows developers to focus on building their core business functionality, rather than spending time on boilerplate subscription and billing code.

How does Laravel Spark integrate with Stripe and Braintree?

Laravel Spark provides seamless integration with Stripe and Braintree for subscription billing. You can easily configure your Spark application to use either of these payment gateways. Once configured, Spark will handle all aspects of subscription billing, including creating subscriptions, handling plan changes, and managing cancellations. This allows you to focus on your core business logic, rather than dealing with the complexities of subscription billing.

Can I create free plans with Laravel Spark?

Yes, Laravel Spark allows you to create free plans. This can be useful if you want to offer a free tier of your service, or if you want to provide a free trial period before charging customers. To create a free plan, you simply need to set the price of the plan to 0 when you define it in your Spark service provider.

How can I customize the user interface of my Laravel Spark application?

Laravel Spark provides a clean, modern user interface out of the box. However, you may want to customize this to match your brand or to provide a unique user experience. Spark makes this easy by using Vue.js for its frontend. This allows you to easily customize the user interface using Vue components. You can also customize the CSS styles used by Spark to match your brand.

How does Laravel Spark handle team billing?

Laravel Spark provides built-in support for team billing. This allows you to charge customers on a per-team basis, rather than per-user. You can easily define team plans in your Spark service provider, and Spark will handle all aspects of team billing, including creating teams, adding and removing team members, and handling plan changes.

Can I use Laravel Spark for a non-subscription business?

While Laravel Spark is primarily designed for subscription-based businesses, it can also be used for non-subscription businesses. You can simply disable the subscription features and use Spark for its other features, such as authentication and user management. However, keep in mind that some of Spark’s features, such as team billing, are specifically designed for subscription businesses.

How does Laravel Spark handle user impersonation?

Laravel Spark provides a powerful user impersonation feature. This allows administrators to impersonate other users, which can be useful for troubleshooting or providing customer support. When an administrator is impersonating a user, they can see exactly what the user sees, and can perform actions as if they were that user. This feature can be easily enabled or disabled in your Spark configuration.

How can I get support for Laravel Spark?

Laravel Spark is a premium product, and comes with support from the Laravel team. If you encounter any issues or have any questions, you can submit a support ticket through the Laravel Spark website. The Laravel team is known for their responsive and helpful support.

Can I use Laravel Spark with other Laravel packages?

Yes, Laravel Spark is designed to work seamlessly with other Laravel packages. You can easily integrate other Laravel packages into your Spark application, allowing you to leverage the full power of the Laravel ecosystem.

How can I get started with Laravel Spark?

To get started with Laravel Spark, you first need to purchase a license from the Laravel Spark website. Once you have a license, you can download the Spark installer and use it to create a new Spark application. From there, you can customize your application to suit your business needs.

Christopher PittChristopher Pitt
View Author

Christopher is a writer and coder, working at Over. He usually works on application architecture, though sometimes you'll find him building compilers or robots.

BrunoSlaravellaravel sparkOOPHPPaaSPHPphp frameworkphp frameworksSaaSsparkstripe
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week