DEV Community

loading...
IT Minds

Upgrading KnitZilla to a full-stack

simmetopia profile image Simon Bundgaard-Egeberg Originally published at insights.it-minds.dk ・4 min read

The why

I have previously written a blog post about a knitting app I made for my wife (https://dev.to/itminds/knitzilla-2lj0).

The app was written in reason, and I partially made it to make something useful for my wife, but also to learn something new. And that is indeed the case with KnitZilla V-2!

The new and improved KnitZilla is made with Laravel as a backend, and a typescript React app as my frontend.

Laravel is a very traditional rest framework, providing utilities for middleware, controllers, and routes.

I am mainly a frontend developer, and that sometimes make my choices concerning which backend technologies I use. I like express.js, because it is simple to get started, and simple to define routes. I steer away from more integrated solutions like asp.net core, because I find them more difficult to make sense of right off the bat.

Why Laravel then? I have a web hotel at a place where there is no CLI access.
It does, however, provide a MySQL database as well. Most of my traditional solutions can't be used since I use CLI access to run migrations and start servers.

With that boring stuff out of the way, let us dig into how I did it!?

The how

First of all, scaffolding the laravel app. I mean, that was easy. After running some apt install on my WSL I was ready to use the scaffolder.

composer create-project --prefer-dist laravel/laravel knitzilla

By default, laravel uses blade templates as frontend rendering engine. For this, this does not fly.

By the very least, I need an experience that gives the same feel as the previous one, and that one worked offline. For that reason, I need an SPA with a service worker.

A React.js Frontend

<!-- spa.blade.php -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Knitzilla</title>
    <link rel="stylesheet" type="text/css" href="{{ mix('css/app.css') }}">
    <link href="https://fonts.googleapis.com/css?family=Rubik:300,400,500,700,900&display=swap" rel="stylesheet">
    <link rel="manifest" href="/manifest.json" />
</head>

<body>
    <div id="app">
        <app></app>
    </div>

    <script src="{{ mix('js/app.js') }}"></script>
</body>

</html>

Very good, we now know how to serve our new web app. But how do we tell laravel that this is React, and how to parse the typescript files?

By default, Laravel uses webpack to bundle all frontend assets. Laravel also provides an easy API for working with webpack called mix.

// webpack.mix.js
const mix = require("laravel-mix");

mix.react("resources/js/app.ts", "public/js") // tells mix this is a react app
    .sass("resources/sass/app.scss", "public/css") // for whatever global css
    .webpackConfig({ // this config defined the typescript loader
        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    loader: "ts-loader", 
                    exclude: /node_modules/
                }
            ]
        },
        resolve: {
            extensions: ["*", ".js", ".jsx", ".vue", ".ts", ".tsx"]
        }
    });

Even though it's just a couple of lines here, it's not on by default.

running npm run watch at this point bundles all the react and CSS assets and puts them in the public folder. From here, it is the job of the laravel framework to serve the web app on the correct paths.

Laravel provides different files for initializing different routes. All these different files have a specific middleware chain attached. For instance, you might not want to auth middleware to run on the first request to get the web app.

For this specific scenario, we have our web routes, which will be used to serve our frontend application, and our API routes, which will have API controllers for the data needed in the app.

To register our spa.blade.php file into laravel, we will create a controller and a new route in web.php route config.

<?php
// SpaController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SpaController extends Controller
{
    public function index()
    {
        return view('spa');
    }
}

return view(spa) will look in the blade template folder, and find a file called spa.blade.php and render that. This will serve the file previously shown.

<?php
// web.php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/{any}', 'SpaController@index')->where('any', '.*');

Here we specify that on any route from the root, we want to serve the index function from the SpaController file.

El service worker

The last part missing here is setting up the service worker.

I am using workbox from google to handle all my service worker related stuff.

// js/src/service-worker.js
importScripts(
    "https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"
);
if (workbox) {
   // service worker related stuff  
}

When the bundling step is done, I will use the workbox CLI to find this template file and create the actual service-worker.js file that will be put in public folder which is statically available to the browser, and from there, it just works.

Discussion

pic
Editor guide