Skip to content

Snippets

Small pieces of config and code I reach for again and again. Copy, paste, ship.

Pulled live from my GitHub Gists, so new ones show up here on their own.

Setting Up Authentication for Laravel Single Page Applications (SPA)

laravel-spa-authentication.mdUpdated

Suppose you have two websites that require authentication:

  1. localhost:8000 (backend)
  2. localhost:3000 (frontend)

Configuration Steps:

  1. Allow origins for https://localhost:3000/
  2. Set the session domain to http://localhost
  3. Configure Sanctum stateful domains for http://localhost:3000
SettingValueFormat
FRONTEND_URLhttps://localhost:3000schema://host:port without / at the end of the URL
SESSION_DOMAINlocalhosthost
SANCTUM_STATEFUL_DOMAINSlocalhost:3000host:port

1. Update config/cors.php

PHP
<?php

return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_methods' => ['*'],
    'allowed_origins' => [env('FRONTEND_URL', 'http://127.0.0.1:8000')],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => true,
];

In allowed_origins, add the FRONTEND_URL in the format schema://hostname, such as https://localhost:3000.

2. Update config/session.php

PHP
<?php

    'domain' => env('SESSION_DOMAIN', null),

Set the SESSION_DOMAIN to the main host, e.g., localhost.

3. Update config/sanctum.php

PHP
<?php

    'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
        '%s%s',
        'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
        env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
    ))),

Add SANCTUM_STATEFUL_DOMAINS only for the main host, e.g., localhost.

Update .env file

APP_NAME=
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000
FRONTEND_URL=http://localhost:3000
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:3000

// etc...

Notes:

  1. The domain in SANCTUM_STATEFUL_DOMAINS must be part of the same domain as SESSION_DOMAIN (e.g., localhost:3000 -> localhost).
  2. The default SESSION_LIFETIME is set to expire after 2 hours.

For more information, refer to the Laravel Sanctum SPA Authentication documentation.

Laravel Batch Transaction Processor

bus.mdUpdated

Simple Description: Asynchronous batch job processor for handling bulk transactions in Laravel using queues and real-time updates.

๐Ÿ“ What is this?

A Laravel-based system that processes large volumes of transactions asynchronously using queued batch jobs. It breaks down large transaction sets into smaller chunks, processes them in parallel, and provides real-time status updates.

๐Ÿ“ File Structure

โ”œโ”€โ”€ Jobs/
โ”‚   โ””โ”€โ”€ LoadSendTransactionsBatch.php    # Main batch job loader
โ”œโ”€โ”€ Services/
โ”‚   โ””โ”€โ”€ SendJobTransactionService.php    # Service orchestrator

๐Ÿ’ป Code Example

PHP
<?php

namespace App\Jobs;

use App\Models\Transaction\Transaction;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Throwable;

class LoadSendTransactionsBatch implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;
    use Batchable;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(
        private int $transactionId,
        private int $userId,
    ) {
    }

    /**
     * @return string
     */
    public function uniqueId(): string
    {
        return $this->transactionId;
    }

    /**
     * Execute the job.
     *
     * @return void
     *
     * @throws Throwable
     */
    public function handle()
    {
        if ($this->batch()->cancelled()) {
            return;
        }

        Transaction::findOrFail($this->transactionId)
            ->getTransactionsToSend()
            ->lazy(500)
            ->each(fn ($transactionDetails) => $this->batch()
                ->add(
                    new SendTransactionsProcess(
                        transactionId: $this->transactionId,
                        transactionDetailId: $transactionDetails->id,
                        userId: $this->userId
                    )
                )
            );
    }
}

PHP
<?php

namespace App\Services\Transaction;

use App\Events\TransactionUpdated;
use App\Exceptions\TransactionException;
use App\Jobs\LoadSendTransactionsBatch;
use App\Models\Transaction\Transaction;
use Illuminate\Support\Facades\Bus;
use Throwable;

class SendJobTransactionService
{
    protected Transaction $transaction;

    protected int $userId;

    /**
     * add the transaction and transaction detail.
     *
     * @param  int  $transactionId
     * @return $this
     */
    public function to(int $transactionId): static
    {
        $this->transaction = Transaction::lockForUpdate()->findOrFail($transactionId);

        return $this;
    }

    /**
     * user who process transaction.
     *
     * @param  int  $userId
     * @return $this
     */
    public function sendBy(int $userId): static
    {
        $this->userId = $userId;

        return $this;
    }

    /**
     * make checks before handling functionality to Send Job transaction.
     *
     * @throws Throwable
     */
    private function check(): void
    {
        throw_if(! $this->transaction->canSendTransaction(), TransactionException::cannotSendTransaction());

        throw_if(! $this->transaction->isFinishedJob(), TransactionException::transactionJobNotCompleted());

        throw_if(! $this->transaction->haveTransactionsToSend(), TransactionException::transactionNotHaveJobs());
    }

    /**
     * handle the logic to send the transaction.
     *
     * @throws Throwable
     */
    public function handle(): void
    {
        $this->check();

        $batch = Bus::batch([
            new LoadSendTransactionsBatch($this->transaction->id, $this->userId),
        ])
            ->allowFailures()
            ->name("transaction-{$this->transaction->id}")
            ->finally(fn () => broadcast(new TransactionUpdated($this->transaction->id)))
            ->dispatch();

        $this->transaction->update([
            'batch_id' => $batch->id,
        ]);
    }
}

โœจ Key Features

FeatureDescription
Chunked ProcessingLoads 500 records at a time to prevent memory issues
Unique JobsPrevents duplicate processing with ShouldBeUniqueUntilProcessing
Batch CancellationStops processing if batch is cancelled
Real-time UpdatesBroadcasts status via websockets
Error HandlingCustom exceptions with descriptive messages
Database LockingPrevents race conditions with lockForUpdate()

PHP CS Fixer Rules - Laravel Pint

pint.jsonUpdated

pint.jsonJSON
{
    "preset": "laravel",
    "rules": {
        "array_indentation": true,
        "array_syntax": {
            "syntax": "short"
        },
        "binary_operator_spaces": {
            "default": "single_space"
        },
        "blank_line_after_namespace": true,
        "blank_line_after_opening_tag": true,
        "blank_line_before_statement": {
            "statements": [
                "return"
            ]
        },
        "braces": true,
        "cast_spaces": true,
        "class_attributes_separation": {
            "elements": {
                "const": "one",
                "method": "one",
                "property": "one",
                "trait_import": "none"
            }
        },
        "class_definition": {
            "multi_line_extends_each_single_line": true,
            "single_item_single_line": true,
            "single_line": true
        },
        "concat_space": {
            "spacing": "none"
        },
        "constant_case": {
            "case": "lower"
        },
        "declare_equal_normalize": true,
        "elseif": true,
        "encoding": true,
        "full_opening_tag": true,
        "fully_qualified_strict_types": true,
        "function_declaration": true,
        "function_typehint_space": true,
        "general_phpdoc_tag_rename": true,
        "heredoc_to_nowdoc": true,
        "include": true,
        "increment_style": {
            "style": "post"
        },
        "indentation_type": true,
        "linebreak_after_opening_tag": true,
        "line_ending": true,
        "lowercase_cast": true,
        "lowercase_keywords": true,
        "lowercase_static_reference": true,
        "magic_method_casing": true,
        "magic_constant_casing": true,
        "method_argument_space": {
            "on_multiline": "ignore"
        },
        "multiline_whitespace_before_semicolons": {
            "strategy": "no_multi_line"
        },
        "native_function_casing": true,
        "no_alias_functions": true,
        "no_extra_blank_lines": {
            "tokens": [
                "extra",
                "throw",
                "use"
            ]
        },
        "no_blank_lines_after_class_opening": true,
        "no_blank_lines_after_phpdoc": true,
        "no_closing_tag": true,
        "no_empty_phpdoc": true,
        "no_empty_statement": true,
        "no_leading_import_slash": true,
        "no_leading_namespace_whitespace": true,
        "no_mixed_echo_print": {
            "use": "echo"
        },
        "no_multiline_whitespace_around_double_arrow": true,
        "no_short_bool_cast": true,
        "no_singleline_whitespace_before_semicolons": true,
        "no_spaces_after_function_name": true,
        "no_spaces_around_offset": {
            "positions": [
                "inside",
                "outside"
            ]
        },
        "no_spaces_inside_parenthesis": true,
        "no_trailing_comma_in_list_call": true,
        "no_trailing_comma_in_singleline_array": true,
        "no_trailing_whitespace": true,
        "no_trailing_whitespace_in_comment": true,
        "no_unneeded_control_parentheses": {
            "statements": [
                "break",
                "clone",
                "continue",
                "echo_print",
                "return",
                "switch_case",
                "yield"
            ]
        },
        "no_unreachable_default_argument_value": true,
        "no_useless_return": true,
        "no_whitespace_before_comma_in_array": true,
        "no_whitespace_in_blank_line": true,
        "normalize_index_brace": true,
        "not_operator_with_successor_space": true,
        "object_operator_without_whitespace": true,
        "ordered_imports": {
            "sort_algorithm": "alpha"
        },
        "psr_autoloading": true,
        "phpdoc_indent": true,
        "phpdoc_inline_tag_normalizer": true,
        "phpdoc_no_access": true,
        "phpdoc_no_package": true,
        "phpdoc_no_useless_inheritdoc": true,
        "phpdoc_scalar": true,
        "phpdoc_single_line_var_spacing": true,
        "phpdoc_summary": false,
        "phpdoc_to_comment": false,
        "phpdoc_tag_type": true,
        "phpdoc_trim": true,
        "phpdoc_types": true,
        "phpdoc_var_without_name": true,
        "self_accessor": true,
        "short_scalar_cast": true,
        "simplified_null_return": false,
        "single_blank_line_at_eof": true,
        "single_blank_line_before_namespace": true,
        "single_class_element_per_statement": {
            "elements": [
                "const",
                "property"
            ]
        },
        "single_import_per_statement": true,
        "single_line_after_imports": true,
        "single_line_comment_style": {
            "comment_types": [
                "hash"
            ]
        },
        "single_quote": true,
        "space_after_semicolon": true,
        "standardize_not_equals": true,
        "switch_case_semicolon_to_colon": true,
        "switch_case_space": true,
        "ternary_operator_spaces": true,
        "trailing_comma_in_multiline": {
            "elements": [
                "arrays"
            ]
        },
        "trim_array_spaces": true,
        "unary_operator_spaces": true,
        "visibility_required": {
            "elements": [
                "method",
                "property"
            ]
        },
        "whitespace_after_comma_in_array": true
    }
}

Automated Telegram Topic Generation using JavaScript

createTopicsTelegram.jsUpdated

createTopicsTelegram.jsJavaScript
const subjects = [
  {
    name: "Quizzes",
    icon_custom_emoji_id: "5433614043006903194",
    text: "Quizzes Topic",
  },
  // etc
];
const telegramToken = "";
const chatId = "";

const createForumTopic = async ({ name, chat_id, icon_custom_emoji_id, text }) => {
  const response = await (
    await fetch(
      `https://api.telegram.org/bot${telegramToken}/createForumTopic?name=${name}&chat_id=${chat_id}&icon_custom_emoji_id=${icon_custom_emoji_id}`
    )
  ).json();

  await (
    await fetch(
      `https://api.telegram.org/bot${telegramToken}/sendMessage?chat_id=${chat_id}&text=${text}&message_thread_id=${response?.result?.message_thread_id}`
    )
  ).json();

  console.log(response);
};

const main = async () => {
  await (
    await fetch(
      `https://api.telegram.org/bot${telegramToken}/sendMessage?chat_id=${chatId}&text=started creating topics ...`
    )
  ).json();

  for (const subject of subjects) {
    await createForumTopic({
      name: subject.name,
      chat_id: chatId,
      icon_custom_emoji_id: subject.icon_custom_emoji_id,
      text: subject.text,
    });

    await new Promise((resolve) => setTimeout(resolve, 5 * 1000));
  }

  await (
    await fetch(
      `https://api.telegram.org/bot${telegramToken}/sendMessage?chat_id=${chatId}&text=finished creating topics ...`
    )
  ).json();
};

main();
router.tsTypeScript
import { createRouter, createWebHistory } from 'vue-router';
import routes from '~pages';

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: routes,
});


router.onError((err) => {
  const messages = [
    'Importing a module script failed', // safari
    'Failed to fetch dynamically imported module' // edge & chrome
  ];

  if (messages.some((message) => err?.message.includes(message))) {
    router.go(0);

    return;
  }
});

export default router;

1. Install Pritter

bash
npm install --save-dev --save-exact prettier

2. Create Prettier Configuration File

Create a .prettierrc file and paste your configuration into it. For more options, refer to the Prettier documentation.

JSON
{
    "arrowParens": "avoid",
    "singleQuote": true,
    "semi": true,
    "bracketSameLine": false,
    "bracketSpacing": true,
    "vueIndentScriptAndStyle": true,
    "trailingComma": "es5",
    "singleAttributePerLine": true,
    "jsxBracketSameLine": true,
    "tabWidth": 2,
    "useTabs": false,
    "printWidth": 100
}

3. Set Up Pre-commit Hook

bash
npm install --save-dev husky lint-staged
npx husky install
npm pkg set scripts.prepare="husky install"
npx husky add .husky/pre-commit "lint-staged"

4. Update package.json with Lint-Staged Configuration

JSON

 "lint-staged": {
    "*.js": "eslint --fix",
    "{**/*,*}.{js,vue,html,css}": "prettier --write"
 }

References