Todd Smith-Salter

Easy JSON API Responses with Laravel

Laravel has evolved so much since I last laid hands on in over a year-and-a-half ago. Eloquent API Resources are a massive boost to those of us looking to easily hammer out standardized JSON API responses. While these are not fully-spec compliant {json:api} responses, they're pretty darn close and good enough for most use cases.

Simply put, an API Resource consumes a database query in the form of a model and spits out structured JSON. In the usual Laravel way, API Resources are easily customized.

Heads up: this is based on Laravel 8.x. and may not work in previous versions.

Create a Resource for a Single Object

So, let's say I want to query a single user. First I'll start by using artistan to create the Resource class file.

php artisan make:resource UserResource

Then in the toArray($request) method, I'll return an array of what I want to expose to the API consumer. This is great, as I can abstract the model properties away from the response and do some basic serialization before responding, if desired.

public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

Now I can simply return the Resource from the controller. By default, the array will be wrapped in a data key. I recommend leaving this setting as is but it can be overridden as per the documentation.

use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::all());
});

The object returned will look similar to the following.

{
    data: {
    	id: 1,
        name: 'Luke Skywalker',
        email: 'l.skywalker@jedi.org',
        created_at: 'timestamp',
        updated_at: 'timestamp'
    }
}

Return a Collection

The JsonResource class has a static collection method to return an array of Resources, or in my case, Users. This is so dang easy I want to weep for joy.

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
    return UserResource::collection(User::all());
});

This returns an array of Users in the data key.

{
    data: [
        {
            id: 1,
            name: 'Luke Skywalker',
            email: 'l.skywalker@jedi.org',
            created_at: 'timestamp',
            updated_at: 'timestamp'
        },
        {
            id: 2,
            name: 'Leia Organa',
            email: 'l.organa@rebelalliance.org',
            created_at: 'timestamp',
            updated_at: 'timestamp'
        },
        ...
    ]
}

More complex customizations can be done by creating a UserCollection resource, which Laravel is smart enough to proxy to a collection of UserResource instances.

Other Goodies

Eloquent API Resources also makes these features seem a breeze to implement:

View the full documentation for the full details of Eloquent API Resources.

Year In Review: 2020

Year In Review: 2020

Review

What Went Well

Vocation

The COVID-19 pandemic threw a wrench in everyone’s years. Thankfully, my job continued for most of the year, in-person, no less. Sadly, my role at NTL Pipelines ended in November but I am thankful for my time there.

Spiritual

Stepped up my personal accountability game by making habits to connect with my mates regularly throughout each week.

Restarted regular therapy in June and dug into a lot of hurts and wounds from my past. I have a lot of hope for a mentally healthy future.

What Didn't Go Well

Personal Health

As a result of my 72-hour-a-week role starting in July 2019, I fell completely off the exercise wagon and didn’t get back into it until well into June. Since then, I’ve run 2-3 times most weeks.

Eating healthy is another story. My sweet tooth became a monster in 2020 and I didn’t do much to curb it. I’ve felt most healthy when eliminating sugar from my diet and getting back to that spot would be wonderful.

I gained 10 lbs since last year due to the poor eating and little exercise. Would like to get back to my peak running weight of around 175 lbs.

Relationships

I let people down this year. Especially my wife. I’ve always struggled to communicate clearly and at opportune times, often holding back what I’m truly feeling. My poor sense of security and personal value means I hold onto my feelings out of fear of a negative response. It’s a vicious cycle I want to get out of with the help of God and my therapist.

Finances

I had a good job, which paid the bills and more. My wife and I have struggled to talk the same language about finances over the years and haven’t been able to make a clear budget, not for lack of trying. We’re still living within our means and not buying excessive material possessions. However, if we are to be stewards of what we’ve been given, keeping ourselves accountable and saving to give away will be important.

New Emerging Opportunities

My role ending at NTL means I have the opportunity to start over in a role better suited to my strengths and to our lifestyle. NTL was great but the hours (50-72 each week) were excessive and led to burn out and contributed to my poor personal health.

I’m excited to explore opportunities in the web development space. I spent the month of December 2020 getting back into javascript (React, NextJS) and php (Laravel). I’m looking forward to finding a great home for my technical expertise.

Music

Album of the Year

2020 and my AOTY is from 2006...living in the past, haha. This is such a great album and gets me going every time I listen to it. Great for when I'm in the flow.

Track of the Year

Favs

Games

Game of the Year

I played the snot out of Breath of the Wild, finally wrapping up a 300 hour play-through in December. Loved it so much I've started a Master Mode play-through, which is stinking hard! Died at the first bokoblin I met. 😱

Runner Up

I only just started playing STAR WARS Jedi: Fallen Order but man, is this game beautiful. I've only scratched the surface but this game feels so immersive. It's not open world, very linear, but it's a great change of pace from BotW's freedom. Sometimes, I just want a game to tell me what's next.

Film + Television

I like watching movies and television shows, especially in the era where one can watch an entire season/series in a few days. I read most of The Expanse book series this year and paired it with watching the excellent first 4 seasons of the television show. I'd have to say, the only Amazon backed season, season four, was one of the better produced shows I've seen in a long time, especially all the sequences on Ilus.

Books

I read/listened to a lot of books this year. There's a distinct theme of science-fiction and fantasy.

  • Saturn Run by John Sandford
  • The Shadow of What Was Lost by James Islington
  • An Echo of Things To Come by James Islington
  • An Astronaut’s Guide to Life on Earth by Chris Hadfield
  • Dune by Frank Herbert
  • Five Feet Apart by Rachael Lippincott
  • Pretty Girls by Karin Slaughter
  • The Burning Sky by Sherry Thomas
  • The Perilous Sea by Sherry Thomas
  • The Immortal Heights by Sherry Thomas
  • The Firebird Trilogy by Kathy Tyers
  • Leviathan Wakes (The Expanse, 1) by James S.A. Corey
  • Caliban’s War (The Expanse, 2) by James S.A. Corey
  • The Calculating Stars by Mary Robinette Kowal
  • Little House On The Prairie by Laura Ingalls Wilder (re-read)
  • The Oracle Year by Charles Soule
  • Gods of Risk (The Expanse, 2.5) by James S.A. Corey
  • Abaddon’s Gate (The Expanse, 3) by James S.A. Corey
  • Cibola Burn (The Expanse, 4) by James S.A. Corey
  • Nemesis Games (The Expanse, 5) by James S.A. Corey
  • The Vital Abyss (The Expanse, 5.5) by James S.A. Corey
  • Babylon’s Ashes (The Expanse, 6) by James S.A. Corey
  • Strange Dogs (The Expanse, 6.5) by James S.A. Corey
  • Persepolis Rising (The Expanse 7) by James S.A. Corey
  • Tiamat’s Wrath (The Expanse 8) by James S.A. Corey
  • On the Banks of Plum Creek by Laura Ingalls Wilder (re-read)
  • The Way of Kings by Brandon Sanderson (re-read)

I'd love to see what others are reading. Connect with me on Goodreads.

Getting Started with Laravel Sanctum

Getting Started with Laravel Sanctum

Update on 01/06/2021: Added links to some helpful articles.


Laravel Sanctum is a slim authentication system for Single Page Applications that don't need the full-meal-deal of an OAuth2 implementation. Sanctum protects an API from Cross Site Forgery Requests by setting a browser cookie and checking for the cookie (or token) on request to ensure requests are coming from approved requesters, be it a first-party SPA or mobile application. It should be paired with a user authentication system.

The majority of this article is a concise summary of what's in the official documentation. If you need more details, follow the link below.

Official Documentation: https://laravel.com/docs/8.x/sanctum

Other helpful sources:

Quotes from the Docs

"Laravel Sanctum provides a featherweight authentication system for SPAs (single page applications), mobile applications, and simple, token based APIs."

"Laravel Sanctum is only concerned with managing API tokens and authenticating existing users using session cookies or tokens. Sanctum does not provide any routes that handle user registration, password reset, etc."

"When Sanctum examines an incoming HTTP request, it will first check for an authentication cookie and, if none is present, Sanctum will then examine the Authorization header for a valid API token."

Installation

Go to the project folder in terminal and add the Sanctum package with composer.

composer require laravel/sanctum

Publish the config and migration files.

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Then run migrations.

php artisan migrate

Config for SPA Authentication

Sanctum uses Laravel's built-in cookie based session authentication services. You get CSRF protection, session auth, and leakage protection of auth credentials via XSS.

Note! Because using cookies, SPA and API must share the same TLD but can be on separate subdomains.

First Party Domains

Decide what domain(s) the SPA will be making requests from using stateful config in config/sanctum.php or set as SANCTUM_STATEFUL_DOMAINS in .env[.local]

The Sanctum config file accepts a comma-separated list of domains. Include port numbers, if required!

"Stateful" means the cookies will be set for these domains. If the cookie exists, the SPA is authenticated. The session cookie being stored in the browser signifies the app knows the user is logged in and can retrieve information about the logged in user. A "stateless" system would require a token to be passed to the API on every single request.

Add Middleware

Add EnsureFrontendRequestsAreStateful::class, to api middleware group in app/Http/Kernel.php file.

CORS & Cookies

Update supports_credentials to true in config/cors.php file. This ensures the applications CORS configuration is returning the Access-Control-Allow-Credentials header with a value of True.

More on CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Gotcha: One thing to note, which tripped me up for a bit. When requesting a cookie from /sanctum/csrf-cookie from a domain other than the API, ensure you set "credentials" : "include" or the cookie won't be saved to browser storage.

Axios make this easy with the following global configuration.

axios.defaults.withCredentials = true;

Lastly, update sessions cookie domain config in config/session.php or .env to allow subdomains.

'domain' => '.domain.com',

Protecting Routes

Attach the sanctum authentication guard to API routes in the routes/api.php file. All requests to these routes require the X-XSRF-TOKEN header or a valid API token header.

use Illuminate\Http\Request;

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Authenticating

CSRF

To authenticate the SPA, the "login" page should first make a request to /sanctum/csrf-cookie endpoint to initialize. This sets an XSRF-TOKEN cookie, which should be passed in an X-XSRF-TOKEN header on subsequent requests.

axios.get('/sanctum/csrf-cookie').then(response => {
    // Login...
});

Logging In

Now a POST request to /login can be implemented.

If successful, subsequent requests to application routes will be automatically authenticated via the session cookie that the Laravel app issued to the client.

If the users' session expires due to lack of activity and a 401 or 419 HTTP response is received, redirect to /login.

Next Up

This is only step one. Now to configure user token authentication with Laravel Fortify a custom implementation.

Restarting my development engine

Restarting my development engine

For the past several years, I've bounced necessarily between sectors such as consulting, media production, construction, and oil & gas. While I'd like to say this has been by choice, each position change has been a result of forces out of my control. While I won't get into the specifics here, it's been a welcome change to get back into web development.

So, here I find myself wanting to and actively getting back into building websites. I've had experience with a number of popular frameworks and CMS's but I've been excited to have the opportunity to flex my thinker (that's my brain 🤔) and learn something completely new to me.

I have to say, developers are not bereft for choice when deciding what to use. I mean, there are so many options it's paralyzing.

Some of the questions I've asked myself are...

This could go on for awhile.

My point is, there are so many options, so many decisions to make, it can so easily bring me to a grinding halt.

Abstractions

Has web development always been like this? I really don't think so. As humans learn and systems evolve, we build abstraction layers above what we consider trivial tasks.

In web development, we've abstracted HTML, CSS, and Javascript behind layers and layers of build tools, modules, references, props, composers, INSERT_FANCY_NAME_HERE.

I'm not saying abstractions are a bad thing. In fact, I think they're the opposite. These abstractions breed technological advancements we wouldn't have dreamed about years ago. It's simply the case that we are option-wealthy. Not only are there thousands of packages already created we can learn from and use as developers, hundreds more are added everyday. We are in a development golden age.

For those us of who have been on pause or stepped away from development for a time, stepping back into this raging torrent is overwhelming. Seriously mind-numbing. I feel like Neo looking at the Matrix for the first time, overwhelmed by the flood of data. I can't mentally parse the signal from the noise.

Summary

My first couple weeks back in the saddle have brought me to surmise the following:

  • Make a quick and educated choice to learn one language/framework/concept and see it to completion.
  • Block new wizz-bang fanciness, the distractions competing for attention as I learn language/framework/concept above.
  • Build something real. I'm rebuilding gpjobs.ca as a NextJS + Laravel API app.

I hope after a few weeks of hard focused work, I have a good return and can be proud of my work. I think that's the best I can ask for.

Sunsetting gpjobs.ca

Sunsetting gpjobs.ca

I created gpjobs.ca as a passion project for a couple reasons.

  1. Create a space where Grande Prairie businesses could connect with great prospects.
  2. To learn new programming tools, specifically Laravel.
  3. To earn some residual income.

While I learned quite a bit about the first two through brute force and involvement in a local job fair as title sponsor, gpjobs.ca was ultimately a fiscal failure and an experience success.

I've moved on to other projects and it's time to shut the doors. Perhaps not forever. gpjobs.ca may return in some metamorphosed form in the future.

Sayonara gpjobs.ca. It was fun.

Focus

When I feel overwhelmed, I'm Peter looking out over the Sea of Galilee noticing the scary storm waves rather than Jesus walking on the water. When Peter lost focus on Christ, he sunk in the waves. When I focus on a singular area letting the fear and unknown slip away from conscious thought, I'm able to do so much more.

Rejection is Hard

For the first time ever, I did not get a job I interviewed for. This was my first technical interview and I knew there was a very good chance I wouldn’t get the job, yet it still doesn’t feel good.

It feels like what I bring to the table isn’t valued. Perhaps it isn’t. Perhaps I wasn’t ready for approval.

Regardless of the other party’s reasons, it’s up to me to decide how to respond to the rejection.

Will I wallow in the sting, feel hopeless, and give up?

Can I take the rejection as a motivator, learn from my mistakes, and move on to another opportunity?

The ball is in my court, so to speak.

Prioritizing Sleep

I have found the most important activity I can do to improve my overall state of mind and health is to get enough sleep.

Once upon a time, I would go to bed late and get up early every night, getting 6 hours of sleep or less. As I’ve grown older and developed habits and routines, I discovered though observation and reflection I need at least seven hours of sleep each night.

When I get seven hours of sleep I feel rested and able to logically think through my day’s priority and the tasks ahead. I can prioritize and make wise choices.

When I get less than seven hours my mind is foggy and fractured. Decisions seem less clear. I am easily distracted. I haven’t built up willpower reserves to begin to make wise choices.

I decided at the beginning of the year to always get seven hours of sleep, even if it means changing my wake-up time because a I stayed up late.

Success isn't an accident

Success isn't an accident

Success, or what you define as success, financial or otherwise, are a direct result of behaviours. The small tasks, the habits you do every day create an environment of motion and progress.

Do you want financial success? Spend less than you make every day.

Do you want to be mentally free to focus on family on the weekends? Use your time and task management system religiously. Don't stray.

Do you want to learn how to code so you can open up an online shop? Practice every day. Just 30 minutes a day adds up to almost 3.5 hours a week.

The only barrier to success is ourselves. "I don't have time" or "I'm so busy" end up just being excuses.

“You will never change your life until you change something you do daily. The secret of your success is found in your daily routine.”
John C. Maxwell

I'm say this as much to myself as anyone: just do it. Success won't come by accident.


Photo by Estée Janssens on Unsplash

Why I deleted Facebook

Derek Silvers on his blog:

Maybe the fact that I use it to share my blog posts is a tiny tiny reason why others are still using it. It’s like I’m still visiting friends in the smoking area, even though I don’t smoke. Maybe if I quit going entirely, it will help my friends quit, too.

I understand this quandry. Facebook has become a lose-lose scenario for me. If I spend time there it distracts me from what's really important, the people around me and the deep work I want to accomplish. My reasons for keeping my Facebook account are selfish and out of fear. "I need it for work." "I'll lose touch with INSERT NAME." "I'll keep it around but won't use it." Excuses. That's all they are.