<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Carlos Talavera</title>
    <description>The latest articles on DEV Community by Carlos Talavera (@charliet1802).</description>
    <link>https://dev.to/charliet1802</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1447987%2F59b94993-06c5-4ef3-ab13-53b4a41e7480.png</url>
      <title>DEV Community: Carlos Talavera</title>
      <link>https://dev.to/charliet1802</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/charliet1802"/>
    <language>en</language>
    <item>
      <title>Principle of Least Astonishment</title>
      <dc:creator>Carlos Talavera</dc:creator>
      <pubDate>Tue, 17 Mar 2026 14:40:40 +0000</pubDate>
      <link>https://dev.to/charliet1802/principle-of-least-astonishment-19i2</link>
      <guid>https://dev.to/charliet1802/principle-of-least-astonishment-19i2</guid>
      <description>&lt;p&gt;When we talk about creating “maintainable” or “sustainable” software, one of the key parts involved in achieving so is the Principle of Least Astonishment (a.k.a. POLA).&lt;/p&gt;

&lt;p&gt;Sounds fancy, but it’s very simple.&lt;/p&gt;

&lt;p&gt;Code is read many more times than it is written. This means that if software developers are going to spend a lot of time reading code, it should be easy to read, right?&lt;/p&gt;

&lt;p&gt;Well, that’s where this principle comes into play.&lt;/p&gt;

&lt;p&gt;One of the traits of code that is easy to read is that if some file, class, method, or any other artifact has a name that says “I do this”, then you expect it to do such a thing because… why wouldn’t it?&lt;/p&gt;

&lt;p&gt;When you expect one thing and see something else, it surprises you. It “astonishes” you. Surprises in life might be nice, but when it comes to software, they’re not.&lt;/p&gt;

&lt;p&gt;It’s not only about misleading names. It’s also about inconsistencies. For example, if you see that in module A the folder structure is one and in module B you find a different one, again, it surprises you.&lt;/p&gt;

&lt;p&gt;So, this principle just says: “Do what you would expect to find. Do what makes sense. Create code so boring that there are no surprises and anyone can understand what’s going on without thinking they’re not smart enough”.&lt;/p&gt;

&lt;p&gt;Boring code is predictable code. Predictable code is reliable.&lt;/p&gt;

&lt;p&gt;If you’re looking for fun and surprises, you can go skydiving or try whatever activities people who love strong thrills usually enjoy.&lt;/p&gt;

&lt;p&gt;But software? It’s not like people say, “I want to do something risky. Let’s create technical debt.” Well, at least I hope they don’t.&lt;/p&gt;

&lt;p&gt;Next time you approach a problem, take a step back and think how you would expect things to work if you were an outsider. That’s the heart of POLA.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>softwaredevelopment</category>
      <category>software</category>
    </item>
    <item>
      <title>What keeps people motivated?</title>
      <dc:creator>Carlos Talavera</dc:creator>
      <pubDate>Tue, 03 Mar 2026 15:00:31 +0000</pubDate>
      <link>https://dev.to/charliet1802/what-keeps-people-motivated-20o0</link>
      <guid>https://dev.to/charliet1802/what-keeps-people-motivated-20o0</guid>
      <description>&lt;p&gt;In the past few months, I’ve been facing some challenges in my current job that made me think about motivation.&lt;/p&gt;

&lt;p&gt;Why do some days feel easier than others?&lt;br&gt;
What actually makes the difference?&lt;br&gt;
Is it having less work — or more meaningful work?&lt;br&gt;
What do we do on the days when everything feels overwhelming and starting seems impossible?&lt;/p&gt;

&lt;p&gt;Each of us has different reasons that keep us going despite adversity.&lt;/p&gt;

&lt;p&gt;The people we love. The money we earn. The dreams we’re building. The hobbies that keep us sane. The responsibilities we carry.&lt;/p&gt;

&lt;p&gt;No reason is more valid than another — and most of the time, it’s a mix.&lt;/p&gt;

&lt;p&gt;As I’ve progressed in my career as a software engineer, I’ve noticed how priorities shift.&lt;/p&gt;

&lt;p&gt;At the beginning, motivation often comes from finishing tasks. You do what’s assigned. You get small, frequent wins. Maybe it’s shipping a feature. Learning a new language. Trying a new framework. Applying a pattern correctly. The feedback loop is short and satisfying.&lt;/p&gt;

&lt;p&gt;But over time, something changes.&lt;/p&gt;

&lt;p&gt;You stop thinking in terms of tasks.&lt;br&gt;
You stop thinking even in terms of projects.&lt;br&gt;
You start thinking in systems.&lt;/p&gt;

&lt;p&gt;And systems are messy.&lt;/p&gt;

&lt;p&gt;Dependencies are no longer just between Service A and Service B. They’re between teams. Between departments. Between priorities. Between people. Sometimes even between companies or regulations.&lt;/p&gt;

&lt;p&gt;You realize that naming something isn’t trivial. A name can shape how a system evolves. Ten years later, that “temporary” decision might still be there — creating misunderstandings, misaligned expectations, and friction that slowly wears teams down.&lt;/p&gt;

&lt;p&gt;The complexity grows. The wins take longer. The impact is less visible.&lt;/p&gt;

&lt;p&gt;So what keeps us motivated when the work becomes less about tasks and more about navigating complexity?&lt;/p&gt;

&lt;p&gt;I’m no expert, but here’s what works for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Break things down — aggressively.&lt;br&gt;
You can’t hold an entire system in your head at once. But you can hold the next small piece. Progress at scale is just small clarity repeated many times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seek deep understanding.&lt;br&gt;
An undefined problem is overwhelming. A grounded problem is actionable. The more precisely you understand the issue, the smaller it feels.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ask for help.&lt;br&gt;
Colleagues. Communities. Mentors. Even AI — as long as you stay critical. Complexity is lighter when shared.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And outside of work?&lt;/p&gt;

&lt;p&gt;Disconnect. Rest. Do what you enjoy. Complexity requires energy — and energy isn’t infinite.&lt;/p&gt;

&lt;p&gt;Motivation, for me, isn’t constant excitement.&lt;br&gt;
It’s learning how to operate even when things feel heavy.&lt;/p&gt;

&lt;p&gt;So I’m curious:&lt;br&gt;
What keeps you motivated when the work stops being simple?&lt;/p&gt;

</description>
      <category>software</category>
      <category>softwareengineering</category>
      <category>careerdevelopment</category>
    </item>
    <item>
      <title>What is the domain and why is it important?</title>
      <dc:creator>Carlos Talavera</dc:creator>
      <pubDate>Tue, 24 Feb 2026 16:05:22 +0000</pubDate>
      <link>https://dev.to/charliet1802/what-is-the-domain-and-why-is-it-important-4o9o</link>
      <guid>https://dev.to/charliet1802/what-is-the-domain-and-why-is-it-important-4o9o</guid>
      <description>&lt;p&gt;One of the most important things I have learned as a software engineer is to design systems around the domain.&lt;/p&gt;

&lt;p&gt;“What’s the domain?” — you might ask.&lt;/p&gt;

&lt;p&gt;In terms of software design, I would define the domain as any useful unit of business.&lt;/p&gt;

&lt;p&gt;For example, let’s say the business is real estate. One domain might be “rentals” and another one might be “sales”. Both have their own rules and logic and represent useful units that give us a full picture of the business.&lt;/p&gt;

&lt;p&gt;That’s the whole point.&lt;/p&gt;

&lt;p&gt;Why would the software — the code, the database entities, the tools, etc. — use technical and obscure names? Why would the software and the business speak two different languages?&lt;/p&gt;

&lt;p&gt;This disconnection creates problems we’ve all seen in software development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product/Business and engineering are talking about the same thing, but using different words. This creates unnecessary confusion and friction&lt;/li&gt;
&lt;li&gt;Business rules and semantics are all over the place. This means that the boundaries are not clear. Unclear boundaries are the perfect recipe for technical debt. This debt implies rigid software that is a headache to maintain and that doesn’t evolve with the product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If, instead, we design around the domain, we create expressive software. So expressive that anyone with business knowledge could understand most of what’s happening even if they don’t know the specifics of how to code.&lt;/p&gt;

&lt;p&gt;Code that is easy to read is one of the fundamental parts of sustainable software.&lt;/p&gt;

&lt;p&gt;The domain is so important that it is the core of any kind of “clean” architecture. Every other layer relies on the domain, while the domain doesn’t rely on anything else (why would it?). This is trivial if I say that in order to understand a system, you have to start by understanding the problem it’s trying to solve.&lt;/p&gt;

&lt;p&gt;It’s so important that there’s even a design philosophy called “Domain-Driven Design” (a.k.a. DDD). &lt;/p&gt;

&lt;p&gt;So now, every time you hear “the domain” in this context, you know what they mean, and if you want to approach a complex system, you know where to start.&lt;/p&gt;

</description>
      <category>ddd</category>
      <category>softwareengineering</category>
      <category>cleancode</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why do we create software?</title>
      <dc:creator>Carlos Talavera</dc:creator>
      <pubDate>Tue, 17 Feb 2026 18:37:54 +0000</pubDate>
      <link>https://dev.to/charliet1802/why-do-we-create-software-4ghl</link>
      <guid>https://dev.to/charliet1802/why-do-we-create-software-4ghl</guid>
      <description>&lt;p&gt;Let’s start with a "simple" question: why do we create software?&lt;/p&gt;

&lt;p&gt;Humans pursue knowledge for two reasons: curiosity and solving problems. Sometimes they mix. You try to fix something and end up loving the learning. But for this reflection, let’s focus on the problem-solving part.&lt;/p&gt;

&lt;p&gt;Every project we work on — personal or professional — starts with a problem. We try to automate boring stuff, prevent incidents, save time, improve accuracy, do what was previously impossible, coordinate people and processes… Basically, we try to make life a little easier.&lt;/p&gt;

&lt;p&gt;In other words: software exists to reduce friction between what we want and what actually happens.&lt;/p&gt;

&lt;p&gt;Sounds simple enough. But then… why does software so often make things worse? Why does it confuse people, break randomly, frustrate users, or make future work a nightmare?&lt;/p&gt;

&lt;p&gt;Not because engineers don’t care. Not because the business is “evil”.&lt;/p&gt;

&lt;p&gt;It happens because motivations aren’t fully aligned. Speed is easy to measure, maintainability isn’t. Shipping is visible; technical debt is invisible. Software feels reversible — like you could just CTRL + Z it later.&lt;/p&gt;

&lt;p&gt;Spoiler alert: you can’t. Complex systems remember. Decisions pile up. Mistakes compound.&lt;/p&gt;

&lt;p&gt;And yet, people still talk about “quality” like it’s a purist argument:&lt;/p&gt;

&lt;p&gt;“The good practices say…”&lt;/p&gt;

&lt;p&gt;Sure. But here’s the simpler truth: if we create software to solve problems, anything that introduces instability, fragility, or long-term friction is incomplete.&lt;/p&gt;

&lt;p&gt;A complete solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works correctly&lt;/li&gt;
&lt;li&gt;Handles functional and non-functional requirements&lt;/li&gt;
&lt;li&gt;Remains understandable&lt;/li&gt;
&lt;li&gt;Can evolve as needs change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything less? It’s not done. It’s just shipped.&lt;/p&gt;

&lt;p&gt;Quality isn’t a fancy ideal. It’s purpose realized.&lt;/p&gt;

&lt;p&gt;And if we forget why we create software in the first place… well, don’t be surprised when it stops solving the problem.&lt;/p&gt;

</description>
      <category>software</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Dockerizing a Next.js Application using a Standalone Build</title>
      <dc:creator>Carlos Talavera</dc:creator>
      <pubDate>Sat, 19 Oct 2024 17:06:17 +0000</pubDate>
      <link>https://dev.to/charliet1802/dockerizing-a-nextjs-application-using-a-standalone-build-2co9</link>
      <guid>https://dev.to/charliet1802/dockerizing-a-nextjs-application-using-a-standalone-build-2co9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; has gained popularity in recent years for enabling applications to be placed inside containers. These containers can be deployed to any environment and will work the same way in all of them, providing a &lt;em&gt;uniform&lt;/em&gt; behavior regardless of the platform where the application runs. These containers use &lt;code&gt;images&lt;/code&gt;, which are a copy or compressed &lt;em&gt;snapshot&lt;/em&gt; of the application. By placing them within a container, they are displayed exactly as they are. This is one of those technologies that some were desperate for, while others don't realize they need it until they hear about it.&lt;/p&gt;

&lt;p&gt;For its part, &lt;a href="https://nextjs.org" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; is the most popular &lt;a href="https://react.dev" rel="noopener noreferrer"&gt;React&lt;/a&gt; framework. As any other JavaScript application that uses a bundler such as &lt;a href="https://webpack.js.org" rel="noopener noreferrer"&gt;webpack&lt;/a&gt; or &lt;a href="https://vitejs.dev" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, for production a compiled version of the project is used. This is known as &lt;code&gt;build&lt;/code&gt;. A build aims to provide the minimum amount of code needed for the application to function the same as it does in development. This ensures that JavaScript files are very lightweight, allowing the browser to fetch and interpret them in the shortest possible time to render the user interface or perform whatever tasks the application requires."&lt;/p&gt;

&lt;p&gt;Next.js, specifically, offers a version that further reduces the build size: the &lt;a href="https://nextjs.org/docs/app/api-reference/next-config-js/output#automatically-copying-traced-files" rel="noopener noreferrer"&gt;Standalone Build&lt;/a&gt;. If we use Docker to create an image for our Next.js application, we can easily deploy that great application that we have built to any environment without worrying about compatibility or additional configurations. In this article, we'll see how to achieve it.&lt;/p&gt;



&lt;h2&gt;
  
  
  Package manager
&lt;/h2&gt;

&lt;p&gt;In my case, I like to use &lt;a href="https://pnpm.io" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt; to reduce the disk size of the &lt;code&gt;node_modules&lt;/code&gt; folder. Therefore, the example of the Next.js Docker image uses this package manager, but you can make slight adjustments to use &lt;a href="https://www.npmjs.com" rel="noopener noreferrer"&gt;npm&lt;/a&gt; or &lt;a href="https://yarnpkg.com" rel="noopener noreferrer"&gt;yarn&lt;/a&gt; if you prefer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js configuration
&lt;/h2&gt;

&lt;p&gt;In the &lt;code&gt;next.config.js&lt;/code&gt; file, we have to specify that the resulting build type will be &lt;code&gt;standalone&lt;/code&gt; when the application is compiled for production. For this, we need to include the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import('next').NextConfig} */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;standalone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, the output of the application will be of type &lt;code&gt;standalone&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;

&lt;p&gt;The file that represents our Docker image is the &lt;a href="https://docs.docker.com/reference/dockerfile/" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt;. Commonly this file is placed in the root of the project. Let's create it step by step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Base image
&lt;/h3&gt;

&lt;p&gt;Every Docker image starts from a base image. In this case, any JavaScript project that runs a server, will need a runtime like &lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;. We'll take as base the Docker image of a Node.js version that is compatible with our project. In my case, I like to use the &lt;a href="https://www.alpinelinux.org" rel="noopener noreferrer"&gt;Alpine&lt;/a&gt; version of the images, since this is more lightweight. However, we have to check that there are no compatibility issues when building the image, otherwise, we have to use the "non-Alpine" version of the image. For this example, I use the &lt;code&gt;node:22.6.0-alpine3.19&lt;/code&gt; image as base.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22.6.0-alpine3.19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We place an alias to recycle it in the different steps or &lt;code&gt;stages&lt;/code&gt; of the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  System and pnpm dependencies
&lt;/h3&gt;

&lt;p&gt;The next stage is to install the dependencies. In this case, only one system dependency is required: &lt;code&gt;libc6-compat&lt;/code&gt;. &lt;a href="https://github.com/nodejs/docker-node?tab=readme-ov-file#nodealpine" rel="noopener noreferrer"&gt;Here&lt;/a&gt; it is mentioned why.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build-deps&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; libc6-compat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since pnpm isn't included by default in Node.js, it is necessary to activate it and set the environment variables so as the installed packages can be cached.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PNPM_HOME="/pnpm"&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PNPM_HOME:$PATH"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack prepare pnpm@latest &lt;span class="nt"&gt;--activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we have to set the working directory to have a clear separation between the system folders and the application folder. In this case, we use &lt;code&gt;/app&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have to copy the files containing the project dependencies information and install them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json pnpm-lock.yaml ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--prefer-frozen-lockfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--frozen-lockfile&lt;/code&gt; and &lt;code&gt;--prefer-frozen-lockfile&lt;/code&gt; arguments are used to respect the versions specified in the &lt;code&gt;lock file&lt;/code&gt; of pnpm.&lt;/p&gt;

&lt;p&gt;To finish with this stage, the &lt;code&gt;sharp&lt;/code&gt; library is added. This is necessary to optimize images in a production environment in Next.js.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm add sharp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full stage looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build-deps&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; libc6-compat

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PNPM_HOME="/pnpm"&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PNPM_HOME:$PATH"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack prepare pnpm@latest &lt;span class="nt"&gt;--activate&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json pnpm-lock.yaml ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--prefer-frozen-lockfile&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm add sharp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the application
&lt;/h3&gt;

&lt;p&gt;The next stage is to compile the Next.js application. This is where the key for making the image work lies, because the rest of the Dockerfile isn't anything different or that you can't find in any other example. At this stage it is necessary to pass as build arguments the environment variables used in the project and set them before generating the build.&lt;/p&gt;

&lt;p&gt;This is because, as there are two times in which the applications work, build time and run time, if the environment variables are not available at run time, all the static assets that use them won't have a value for them and the application won't work properly. In this example, three environment variables are used: &lt;code&gt;NEXT_PUBLIC_BACKEND_URL&lt;/code&gt;, &lt;code&gt;FRONTEND_URL&lt;/code&gt; and &lt;code&gt;JWT_SECRET&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL=$FRONTEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET=$JWT_SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, pnpm is activated, the work directory is set, all the application files are copied and the build is generated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack prepare pnpm@latest &lt;span class="nt"&gt;--activate&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build-deps /app/node_modules ./node_modules&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full stage looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL=$FRONTEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET=$JWT_SECRET&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack prepare pnpm@latest &lt;span class="nt"&gt;--activate&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build-deps /app/node_modules ./node_modules&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the application
&lt;/h3&gt;

&lt;p&gt;The last stage is to run the application. To do this, we first set the Node production environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For personal preference, Next.js telemetry is disabled. That is, we basically don't send our application data to Vercel to improve Next.js through error diagnosis and usage metrics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_TELEMETRY_DISABLED=1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, as a good practice, it is recommended to use a &lt;code&gt;non-root&lt;/code&gt; user in Docker images. This, for instance, avoids security breaches in case the container has access to the &lt;code&gt;host&lt;/code&gt; network. To do this, a &lt;code&gt;nodejs&lt;/code&gt; group and a &lt;code&gt;nextjs&lt;/code&gt; user are added and assigned the &lt;code&gt;.next&lt;/code&gt; folder property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 nodejs
&lt;span class="k"&gt;RUN &lt;/span&gt;adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 nextjs
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; .next
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;nextjs:nodejs .next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, the files generated by the &lt;code&gt;standalone build&lt;/code&gt; are copied to create the same structure of the default build of Next.js.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/standalone ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/public ./public&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we created the &lt;code&gt;nextjs&lt;/code&gt; user, we need to specify that this will be the user to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nextjs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Likewise, it is required to specify the exposed port of the container, as well as the Node port and the &lt;code&gt;hostname&lt;/code&gt; that will be used, which will be &lt;code&gt;0.0.0.0&lt;/code&gt; since we don't know the exact address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=3000&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HOSTNAME="0.0.0.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, the environment variables for the application runtime are specified from the build arguments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL=$FRONTEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET=$JWT_SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specified environment variables in a &lt;code&gt;docker-compose.yml&lt;/code&gt; file can be used, as well as when running the container, however, it wouldn't make sense for the environment variables in this context to be different at build time and run time.&lt;/p&gt;

&lt;p&gt;Finally, we run the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Complete file
&lt;/h3&gt;

&lt;p&gt;The complete Dockerfile looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22.6.0-alpine3.19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build-deps&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; libc6-compat

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PNPM_HOME="/pnpm"&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PNPM_HOME:$PATH"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack prepare pnpm@latest &lt;span class="nt"&gt;--activate&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json pnpm-lock.yaml ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--prefer-frozen-lockfile&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm add sharp

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL=$FRONTEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET=$JWT_SECRET&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack prepare pnpm@latest &lt;span class="nt"&gt;--activate&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build-deps /app/node_modules ./node_modules&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_TELEMETRY_DISABLED=1&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 nodejs
&lt;span class="k"&gt;RUN &lt;/span&gt;adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 nextjs
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; .next
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;nextjs:nodejs .next

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/standalone ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/public ./public&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nextjs&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=3000&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HOSTNAME="0.0.0.0"&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_PUBLIC_BACKEND_URL=$NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; FRONTEND_URL=$FRONTEND_URL&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET=$JWT_SECRET&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also find the file in this &lt;a href="https://gist.github.com/carlos-talavera/600bbe58949237ece5e990efd597ac87" rel="noopener noreferrer"&gt;gist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Creating a Docker image for a Next.js application can be daunting at first because of all the considerations we have to take into account. In addition, there is the popular belief that self-hosting a Next.js application, i. e., outside Vercel, is complicated. It isn't. By understanding the key parts, it's actually simple.&lt;/p&gt;

&lt;p&gt;I hope that with this information you can dockerize your Next.js application without problems. And you know the drill, if you have any question or want to share something, leave it in the comments :)&lt;/p&gt;

</description>
      <category>docker</category>
      <category>nextjs</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Integrating Laravel Sanctum with Next.js SSR: Key Points</title>
      <dc:creator>Carlos Talavera</dc:creator>
      <pubDate>Tue, 15 Oct 2024 18:18:05 +0000</pubDate>
      <link>https://dev.to/charliet1802/integrating-laravel-sanctum-with-nextjs-ssr-key-points-45ff</link>
      <guid>https://dev.to/charliet1802/integrating-laravel-sanctum-with-nextjs-ssr-key-points-45ff</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nextjs.org" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; is a &lt;a href="https://react.dev" rel="noopener noreferrer"&gt;React&lt;/a&gt; framework which has gained popularity in the recent years because of its practical approach to route handling, SEO support, caching mechanisms for requests and the different rendering strategies it provides. In a nutshell, by supporting the use of a server, it allows to fetch data on the server instead of fetching it on the client (the browser, for instance). This allows for reduced response times, since once the user can see the interface the data is already available, as opposed to having to wait for the information to be fetched after the interface is visible. This is known as &lt;em&gt;Server-Side Rendering&lt;/em&gt;, or in short, &lt;a href="https://nextjs.org/docs/app/building-your-application/rendering" rel="noopener noreferrer"&gt;SSR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For its part, &lt;a href="https://laravel.com/docs/11.x/sanctum" rel="noopener noreferrer"&gt;Laravel Sanctum&lt;/a&gt; provides a simple way to authenticate applications that consume an API developed in Laravel. There are two authentication methods: using session &lt;code&gt;cookies&lt;/code&gt; or API &lt;code&gt;tokens&lt;/code&gt;. The first approach is perhaps not as well known. It is based on storing cookies in the browser that are sent with every request and verified by the API, hence it only works for web applications, where there is a browser. The second one is well known. A token is assigned to the user and as long as this token is included in the request headers, the user will be authenticated.&lt;/p&gt;

&lt;p&gt;In this article we'll explore authentication using cookies, as this is where problems arise when trying to make requests from the server instead of from the client. Moreover, we have to take advantage of this method because it protects against &lt;a href="https://owasp.org/www-community/attacks/csrf" rel="noopener noreferrer"&gt;CSRF&lt;/a&gt; attacks, which is impossible using API tokens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article talks about the key points, a few details are presented, but it's purely conceptual. At the end there's a link to a GitHub repository with the detailed implementation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;When client-server requests are made, browser cookies are automatically sent. Besides, if there are cookies in the response, they are just simply stored in the browser without any intervention. However, when making a server-server request, cookies don't exist as such, because cookies are a concept of the browser, the server doesn't know anything about them. So, if you want to make a request from the server that includes cookies, you need to ask the browser for them and explicitly send them in the &lt;code&gt;Cookie&lt;/code&gt; header. We'll see how to do this in the Next.js section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laravel
&lt;/h2&gt;

&lt;p&gt;It's possible to install Sanctum and add all the necessary configurations, however, this takes time and because of this, Laravel offers us &lt;a href="https://laravel.com/docs/11.x/starter-kits#laravel-breeze" rel="noopener noreferrer"&gt;Breeze&lt;/a&gt;. Breeze creates the entire skeleton needed to implement authentication. It includes routes to log in and log out, register, verify email and reset password.&lt;/p&gt;

&lt;p&gt;It also adds the necessary configuration for CORS and Sanctum so that we can focus on starting to develop our application. After following the installation steps of the previous link, there will be a part where it will ask us the type of application we want to use Breeze with. We'll select the &lt;code&gt;API&lt;/code&gt; option so that it only gives us what we need to connect from Next.js.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating UserResource
&lt;/h3&gt;

&lt;p&gt;We have to create an &lt;code&gt;UserResource&lt;/code&gt; to return the user information in a standardized way. We first create the &lt;code&gt;resource&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:resource UserResource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we specify the fields as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * Transform the resource into an array.
 *
 * @return array&amp;lt;string, mixed&amp;gt;
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'emailVerifiedAt'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email_verified_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Use camelCase so it matches the naming convention in JavaScript&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Returning the authenticated user after login and registration
&lt;/h3&gt;

&lt;p&gt;We have to modify the &lt;code&gt;store&lt;/code&gt; method in &lt;code&gt;AuthenticatedSessionController.php&lt;/code&gt; to return the authenticated user after login. The same applies to the &lt;code&gt;store&lt;/code&gt; method in &lt;code&gt;RegisteredUserController.php&lt;/code&gt; after registering an user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Http/Controllers/Auth/AuthenticatedSessionController.php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Handle an incoming authentication request.
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;LoginRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;regenerate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;UserResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Http/Controllers/Auth/RegisteredUserController.php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Handle an incoming registration request.
 *
 * @throws \Illuminate\Validation\ValidationException
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'max:255'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lowercase'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'max:255'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'unique:'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'confirmed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Rules\Password&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nf"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Registered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;UserResource&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Next.js section we'll see why we have to do this.&lt;/p&gt;

&lt;h3&gt;
  
  
  CORS
&lt;/h3&gt;

&lt;p&gt;Breeze will add an environment variable &lt;code&gt;FRONTEND_URL&lt;/code&gt; that will contain the address of our Next.js application, which runs in &lt;code&gt;http://localhost:3000&lt;/code&gt; by default. To be able to share cookies, it's also important that there is support for credentials. Therefore, the CORS configuration should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/cors.php&lt;/span&gt;
&lt;span class="s1"&gt;'paths'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="s1"&gt;'allowed_methods'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="s1"&gt;'allowed_origins'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FRONTEND_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:3000'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;

&lt;span class="s1"&gt;'allowed_origins_patterns'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;

&lt;span class="s1"&gt;'allowed_headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="s1"&gt;'exposed_headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;

&lt;span class="s1"&gt;'max_age'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="s1"&gt;'supports_credentials'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Session domains
&lt;/h3&gt;

&lt;p&gt;Session cookies can only be shared between subdomains that exist within the same domain. In the case of a local application, since there are no subdomains unless we use some &lt;code&gt;reverse proxy&lt;/code&gt;, applications are accessed via &lt;code&gt;localhost:{{PORT}}&lt;/code&gt;. Therefore, we have to make sure that the session domain is &lt;code&gt;localhost&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt; (using the IP or the &lt;code&gt;localhost&lt;/code&gt; alias may or not work depending on the operating system and hosts configuration).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;SESSION_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, it's important to use a dot before the domain so that all the subdomains are allowed. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;SESSION_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;.example.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sanctum domains
&lt;/h3&gt;

&lt;p&gt;Sanctum domains are the hosts or addresses from which it is allowed to access the sessions created by this means. Breeze also takes care of adding &lt;code&gt;FRONTEND_URL&lt;/code&gt; to the domains list. The configuration looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/sanctum.php&lt;/span&gt;
&lt;span class="s1"&gt;'stateful'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SANCTUM_STATEFUL_DOMAINS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'%s%s%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;Sanctum&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;currentApplicationUrlWithPort&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FRONTEND_URL'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;parse_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FRONTEND_URL'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;PHP_URL_HOST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;))),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason behind using &lt;code&gt;parse_url&lt;/code&gt; is to remove the &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt; protocol from the frontend URL, since a domain is just the alias of an address, without including the communication protocol. &lt;code&gt;parse_url(env('FRONTEND_URL'), PHP_URL_HOST)&lt;/code&gt; can be read this way: "Take this URL, &lt;code&gt;FRONTEND_URL&lt;/code&gt;, and just keep the &lt;code&gt;PHP_URL_HOST&lt;/code&gt; component, which is the host or URL address". This is a necessary process since &lt;code&gt;FRONTEND_URL&lt;/code&gt; must include the protocol because that's how it's required by CORS configuration.&lt;/p&gt;

&lt;p&gt;In production, it is advisable to eliminate all the local domains (&lt;code&gt;localhost&lt;/code&gt;, &lt;code&gt;127.0.0.1&lt;/code&gt;), because it should only be possible to authenticate from non-local addresses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;a href="https://nextjs.org/docs/app" rel="noopener noreferrer"&gt;App Router&lt;/a&gt; is used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here comes the fun part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment variables
&lt;/h3&gt;

&lt;p&gt;Initially, only the backend URL is needed. We'll call this variable &lt;code&gt;NEXT_PUBLIC_BACKEND_URL&lt;/code&gt;. The &lt;code&gt;NEXT_PUBLIC&lt;/code&gt; prefix is added so that the variable is available in client components, although it will also be used in the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env
&lt;/span&gt;&lt;span class="py"&gt;NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We must not forget the &lt;code&gt;http&lt;/code&gt; protocol because otherwise the URL will be interpreted as relative to the frontend project (as if it was a subfolder), and it won't work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constants definition
&lt;/h3&gt;

&lt;p&gt;Several constants that will be used in differente parts of the application are defined for routes and URL parameters to determine what to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/constants/index.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LOGIN_ROUTE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Expired session&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EXPIRED_SESSION_PARAM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expired_session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EXPIRED_SESSION_ROUTE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;LOGIN_ROUTE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;EXPIRED_SESSION_PARAM&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  axios configuration
&lt;/h3&gt;

&lt;p&gt;I find it practical to use &lt;code&gt;axios&lt;/code&gt; because of the facility it provides to create an instance with a configuration that includes a base URL, credentials support, the CSRF token and other predefined properties, and because of the interceptors that will be useful to handle authentication errors. This is the axios configuration that I use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/axios.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EXPIRED_SESSION_ROUTE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LOGIN_ROUTE&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/constants&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AxiosError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AxiosInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AxiosResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axiosInstance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_BACKEND_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;withCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;withXSRFToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// * Important so we don't get HTML responses&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// "Do not use cached content without validating with the server"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Add an interceptor to redirect to the login page if the server responds with a 401 (unauthorized)&lt;/span&gt;
&lt;span class="nx"&gt;axiosInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interceptors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AxiosError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;LOGIN_ROUTE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// We're on the client side&lt;/span&gt;
        &lt;span class="c1"&gt;// It's important to check that we're not on the login page, otherwise we'll end up in an infinite loop&lt;/span&gt;
        &lt;span class="c1"&gt;// The server side redirects should be handled by a ServerSideRequestsManager class&lt;/span&gt;
        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EXPIRED_SESSION_ROUTE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;axiosInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When does a &lt;code&gt;401 Unauthorized&lt;/code&gt; error happen? When the CSRF token is valid, but the session has already expired, or the domain isn't allowed by Sanctum. The expired session parameter will serve to identify if expiring the session is needed in the middleware. This way there's no need to create a new route to achieve this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing session in client and server
&lt;/h3&gt;

&lt;p&gt;Here are two parts: having the user's information available in client and server components, and knowing if the user is authenticated.&lt;/p&gt;

&lt;p&gt;For the former, it is required to create our own cookie, since the cookies that Laravel provides don't guarantee a valid session because they can simply exist for making an API request. This cookie should be &lt;code&gt;HttpOnly&lt;/code&gt; for secutiry reasons, so that it can be accessed from the client through an &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers" rel="noopener noreferrer"&gt;API Route Handler&lt;/a&gt; and from the server can be accessed directly through the &lt;code&gt;cookies&lt;/code&gt; method that Next.js provides. From the client side, authenticated user's information will be preserved using a global state through React context.&lt;/p&gt;

&lt;p&gt;This cookie will contain the authenticated user's information in a JSON Web Token (&lt;code&gt;JWT&lt;/code&gt;) that will be created with a library that is compatible with the &lt;code&gt;edge runtime&lt;/code&gt; that Next.js utilizes in the middleware, because if you try to access user's information from the middleware and that library uses an API not available in that runtime, an error would occur. The &lt;a href="https://www.npmjs.com/package/jose" rel="noopener noreferrer"&gt;jose&lt;/a&gt; library is an excellent option for this purpose. The &lt;code&gt;secret&lt;/code&gt; to encrypt and decrypt the JWT should be in an environment variable. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;super_secret_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without including &lt;code&gt;NEXT_PUBLIC&lt;/code&gt; because it's only needed server-side. For the second part, knowing if the user is authenticated, we simply need to check in the middleware if the cookie exists and validate its content through the utilized library to generate the JWT. If Role-Based Access Control (&lt;code&gt;RBAC&lt;/code&gt;) is implemented, then the user should include the role and we should use it to determine where to redirect them to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sanctum cookies retrieval
&lt;/h3&gt;

&lt;p&gt;It is important to retrieve the Sanctum cookies before login by making a &lt;code&gt;GET&lt;/code&gt; request to the &lt;code&gt;/sanctum/csrf-cookie&lt;/code&gt; route, as is mentioned in the &lt;a href="https://laravel.com/docs/11.x/sanctum#spa-authenticating" rel="noopener noreferrer"&gt;docs&lt;/a&gt;. If this process is not done, you'll always receive a &lt;code&gt;419 Unknown status&lt;/code&gt; error with the message "CSRF token mismatch". After login, a request to &lt;code&gt;/api/user&lt;/code&gt; has to be made to get the authenticated user's information and the new cookies that represent the session with the user already authenticated. This always applies when you want to renew a session. Otherwise, a &lt;code&gt;401 Unauthorized&lt;/code&gt; will be received.&lt;/p&gt;

&lt;h3&gt;
  
  
  Login
&lt;/h3&gt;

&lt;p&gt;The login process is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve Sanctum cookies&lt;/li&gt;
&lt;li&gt;Log in to Laravel&lt;/li&gt;
&lt;li&gt;Use the authenticated user to generate the cookie with the JWT (this is why we need Laravel to return the user's data)&lt;/li&gt;
&lt;li&gt;Register the user in the global state&lt;/li&gt;
&lt;li&gt;Redirect to homepage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Manual logout
&lt;/h3&gt;

&lt;p&gt;If the user logs out voluntarily, then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log out in Laravel&lt;/li&gt;
&lt;li&gt;Delete the cookie with the JWT (expire it)&lt;/li&gt;
&lt;li&gt;Clear global state (set user to &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Redirect to login page&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Making requests from the server
&lt;/h3&gt;

&lt;p&gt;This is, in my opinion, the most important section, since the secret for successful requests lies in request headers. To make requests from the server, regardless of the type, it is fundamental to send three headers: &lt;code&gt;Referer&lt;/code&gt;, &lt;code&gt;Cookie&lt;/code&gt; and &lt;code&gt;X-XSRF-TOKEN&lt;/code&gt;. The first one, which is the origin of the request, must be the frontend URL. This is very important because this way a domain allowed by Sanctum is used and we wil avoid getting the &lt;code&gt;401 Unauthorized&lt;/code&gt; error. Therefore, there should be another environment variable, which will only be used server-side, called &lt;code&gt;FRONTEND_URL&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;FRONTEND_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the second header cookies must be retrieved and converted into a string. Not including the cookies is the same as not having a session, so we will get the same &lt;code&gt;401 Unauthorized&lt;/code&gt; error if we don't include them.&lt;/p&gt;

&lt;p&gt;The third one is to specify the CSRF token as it's done in client-side requests. To get this value we simply access the &lt;code&gt;XSRF-TOKEN&lt;/code&gt; cookie. Using axios, the example for a &lt;code&gt;GET&lt;/code&gt; request should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axiosInstance&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Referer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FRONTEND_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axiosInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Making requests from the server
&lt;/h3&gt;

&lt;p&gt;It is recommended to use &lt;a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations" rel="noopener noreferrer"&gt;Server Actions&lt;/a&gt; for mutations that perform the same logic that would be performed server-side. &lt;/p&gt;

&lt;h3&gt;
  
  
  Setting cookies after making requests from the server
&lt;/h3&gt;

&lt;p&gt;Originally, Sanctum cookies are updated after every request client-side, so that the session remains valid. However, in the server, cookies are not &lt;em&gt;magically&lt;/em&gt; set after receiving every response. This could be achieved if every server response was a &lt;code&gt;NextResponse&lt;/code&gt; that includes that information.&lt;/p&gt;

&lt;p&gt;Nevertheless, this option is hard to implement for two reasons: you can't directly type a &lt;code&gt;NextResponse&lt;/code&gt;, you can just use a type guard that works at run time; a &lt;code&gt;NextResponse&lt;/code&gt; represents the response of the server, so anything that comes after a &lt;code&gt;NextResponse&lt;/code&gt; is received would be ignored and nothing but the content of this response would be rendered. Handling these subtleties adds complexity to server components.&lt;/p&gt;

&lt;p&gt;Therefore, an easy option is to have a &lt;code&gt;SetCookies&lt;/code&gt; client component  that has a hidden &lt;code&gt;input&lt;/code&gt; which takes care of making a request to the Next.js &lt;code&gt;/api/user&lt;/code&gt; route, which in turn takes care of making a request to the Laravel &lt;code&gt;/api/user&lt;/code&gt; route. Laravel response will be received by Next.js &lt;code&gt;API Route Handler&lt;/code&gt; and when the response from this route reaches the &lt;code&gt;SetCookies&lt;/code&gt; component, it will set the cookies in the browser because it's a client component. This component should be present in all the routes where cookie renewal is needed, so that placing it in a page header or a common layout is the best.&lt;/p&gt;

&lt;p&gt;Cookies from the server response could be passed in an object to the &lt;code&gt;SetCookies&lt;/code&gt; component and set them in the browser and avoid an extra request, however, this wouldn't allow for updating &lt;code&gt;HttpOnly&lt;/code&gt; cookies if they exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error handling in the server
&lt;/h3&gt;

&lt;p&gt;Two common errors will be the &lt;code&gt;401 Unauthorized&lt;/code&gt; error and the &lt;code&gt;419 Unknown content&lt;/code&gt; error. When the first happens, it is necessary to redirect to the expired session route, which will be responsible for expiring the cookie with the JWT from the middleware and redirect to the login page. In this process we have to be careful about removing the expired session parameter so as not to cause an infinite redirection loop in the middleware. For the second error, the request must be retried after renewing the cookies by following the same process that is made when setting the cookies after a request server-side. After this, any other error can occur, but not the same.&lt;/p&gt;

&lt;p&gt;In server components responsible for fetching the data and rendering it, there must be a component in charge of displaying the error. Because of this, and in general for a proper error handling, it is important to standardize Laravel API responses so as to be able to create generic methods and components that make requests and render the appropriate content. This process of standardizing is known as &lt;strong&gt;serializing&lt;/strong&gt;, therefore, this response would be &lt;strong&gt;serializable&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link to GitHub repository
&lt;/h2&gt;

&lt;p&gt;The detailed implementation with an example of a page that fetches all the users paginated and the rest of the pages that Breeze provides can be found &lt;a href="https://github.com/carlos-talavera/nextjs-laravel-breeze" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Integrating Laravel Sanctum with Next.js SSR isn't an easy task at first. There are many subtleties and problems that I encountered while developing a project with these requirements, and I couldn't find solutions anywhere, not even in the &lt;a href="https://github.com/laravel/breeze-next" rel="noopener noreferrer"&gt;official example&lt;/a&gt; of Laravel Breeze with Next.js. However, by understanding the important concepts and the role of each party involved, this integration can be greatly simplified, combining the best of both worlds.&lt;/p&gt;

&lt;p&gt;I hope that with these key points and the repository that contains the example, you can achieve it. If you have questions or want to share something, leave it in the comments :)&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>laravel</category>
      <category>php</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Using Redis for Caching in Laravel: A Step-by-Step Guide</title>
      <dc:creator>Carlos Talavera</dc:creator>
      <pubDate>Sun, 29 Sep 2024 21:48:48 +0000</pubDate>
      <link>https://dev.to/charliet1802/using-redis-for-caching-in-laravel-a-step-by-step-guide-3j4h</link>
      <guid>https://dev.to/charliet1802/using-redis-for-caching-in-laravel-a-step-by-step-guide-3j4h</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://laravel.com" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt; is, without fear of contradiction, the most popular PHP framework, and among the most popular within web development. &lt;a href="https://redis.io" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; is, for its part, an in-memory database widely used for caching storage due to the fact that data is stored in key-value pairs, which makes its management easier. In this article we'll see how to set up Redis in a Laravel application and what is to be considered when handling cache keys.&lt;/p&gt;



&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Make sure you have Redis installed on your computer. You can use &lt;a href="https://hub.docker.com/_/redis" rel="noopener noreferrer"&gt;Docker's official image&lt;/a&gt;, or install it directly on &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-windows/" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;, &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-mac-os/" rel="noopener noreferrer"&gt;MacOS&lt;/a&gt; or &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/" rel="noopener noreferrer"&gt;Linux&lt;/a&gt;. In the case of Linux, it varies from distribution to distribution, however, the common thing is that it's available through the package manager of the operating system, usually under the name of &lt;code&gt;redis&lt;/code&gt; or &lt;code&gt;redis-cli&lt;/code&gt;. Once you confirm that it's installed and the connection works, let's see how to set it up in Laravel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Redis in Laravel
&lt;/h2&gt;

&lt;p&gt;So far we have simply installed the Redis server in our computer, but we need to have a way to connect from a PHP application. For this there are two options, using &lt;a href="https://github.com/phpredis/phpredis" rel="noopener noreferrer"&gt;phpredis&lt;/a&gt;, a PHP extension, or &lt;a href="https://github.com/predis/predis" rel="noopener noreferrer"&gt;predis&lt;/a&gt;, a library. None is better than the other, they just have different objectives and scopes. &lt;code&gt;phpredis&lt;/code&gt; has the advantage that it allows you to connect to Redis from any PHP project that is executed in that computer, whereas &lt;code&gt;predis&lt;/code&gt;, being a library, only allows to connect from the projects where it's installed. In my case, I have experience with &lt;code&gt;phpredis&lt;/code&gt;, so let's see how to install it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing phpredis
&lt;/h3&gt;

&lt;p&gt;The installation guide for several operating systems can be found &lt;a href="https://github.com/phpredis/phpredis/blob/develop/INSTALL.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Again, in Linux, if your distribution is not listed in the guide, my recommendation is to look in your distribution's documentation for the installation process. This process is often to install the package using the package manager and uncomment the extension(s) in the PHP configuration files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel configuration
&lt;/h3&gt;

&lt;p&gt;The first thing we need to do is make sure that Redis configuration in &lt;code&gt;config/database.php&lt;/code&gt; is the right one. It must look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/database.php&lt;/span&gt;
&lt;span class="s1"&gt;'redis'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s1"&gt;'client'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_CLIENT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'phpredis'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="s1"&gt;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s1"&gt;'cluster'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_CLUSTER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'redis'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'prefix'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_PREFIX'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'APP_NAME'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'laravel'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'_database_'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_URL'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'host'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_HOST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'username'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'port'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_PORT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'6379'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="s1"&gt;'cache'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_URL'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'host'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_HOST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'username'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'port'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_PORT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'6379'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_CACHE_DB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we must set the necessary environment variables to use Redis as a cache and connect. Here it is assumed that &lt;code&gt;phpredis&lt;/code&gt; is used, that Redis is installed locally and not in a Docker container, that you left the default port when installing Redis, and that you didn't set up any password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env
&lt;/span&gt;&lt;span class="py"&gt;CACHE_STORE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;redis&lt;/span&gt;
&lt;span class="py"&gt;CACHE_PREFIX&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;laravel_cache&lt;/span&gt;

&lt;span class="py"&gt;REDIS_CLIENT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;phpredis&lt;/span&gt;
&lt;span class="c"&gt;# For Docker, use the name of the service
&lt;/span&gt;&lt;span class="py"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
&lt;span class="py"&gt;REDIS_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;null&lt;/span&gt;
&lt;span class="py"&gt;REDIS_PORT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;6379&lt;/span&gt;

&lt;span class="py"&gt;REDIS_DB&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;REDIS_CACHE_DB&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;REDIS_URL&lt;/code&gt; is used when you want to specify the connection with a single URL, for example, &lt;code&gt;tcp://127.0.0.1:6379?database=0&lt;/code&gt;. Otherwise it's needed to specify the rest of the fields so that Laravel forms this URL at the end and connects. On the other hand, the cache prefix is important because we'll have to use it when searching keys. In the case of &lt;code&gt;REDIS_DB&lt;/code&gt; and &lt;code&gt;REDIS_CACHE_DB&lt;/code&gt;, it's just a way to separate distinct databases depending on the purpose. The first one is the default one if Redis is used without specifying the database, while the second one only takes care of the cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Redis in Laravel
&lt;/h2&gt;

&lt;p&gt;Once the configuration is ready, it's time to use Redis in our application. Laravel provides us a &lt;code&gt;Illuminate\Support\Facades\Cache&lt;/code&gt; class as part of its &lt;code&gt;facades&lt;/code&gt; to make all sorts of operations (if you didn't know, &lt;em&gt;facade&lt;/em&gt; is a &lt;a href="https://refactoring.guru/design-patterns/facade" rel="noopener noreferrer"&gt;design pattern&lt;/a&gt;). It's important to note that when using these methods, it's not necessary to use the Redis prefix, since Laravel adds it automatically. The basic methods are presented below:&lt;/p&gt;

&lt;h3&gt;
  
  
  Get an element
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// value or null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check if a key exists
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// do something&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add or overwrite an element
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;put&lt;/code&gt; method adds or overwrites a cache element, that is, if it doesn't exist it creates it, and if it exists it updates it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$minutes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As seen above, the time in minutes can be specified. If not specified, the element will persist until it is explicitly deleted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add several elements
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;putMany&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'key1'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'value1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'key2'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'value2'&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$minutes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to &lt;code&gt;put&lt;/code&gt;, with the difference that multiple elements are stored at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add an element if it doesn't exist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$minutes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to &lt;code&gt;put&lt;/code&gt;, with the difference that &lt;code&gt;add&lt;/code&gt; only adds an element if it doesn't exist, so it doesn't overwrite the previous one if there is one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete an element
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Clear the cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method deletes all the cache elements, so it must be used with caution. &lt;/p&gt;

&lt;h3&gt;
  
  
  Get or add an element for a fixed time if it doesn't exist
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;remember&lt;/code&gt; method is useful when getting or adding an element to the cache for a fixed time is needed if it doesn't exist yet, i. e., it doesn't overwrite and works like &lt;code&gt;add&lt;/code&gt;. Besides this, its purpose is that by using a &lt;code&gt;closure&lt;/code&gt;, complex operations can be done inside the function to determine the value to be obtained or added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\DB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get or add an element if it doesn't exist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Illuminate\Support\Facades\DB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;rememberForever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to &lt;code&gt;remember&lt;/code&gt;, with the difference that here no time is specified and the element persists until it is explicitly deleted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get and element and then delete it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;pull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method obtains the value associated to the key, stores it on the &lt;code&gt;$value&lt;/code&gt; variable and removes the element from the cache. Clearly, it must be used with care and always assigned to a variable so as not to lose the information. You can find more methods in the &lt;a href="https://laravel.com/docs/11.x/cache" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example of a pattern search
&lt;/h2&gt;

&lt;p&gt;A common situation is that we need to search for all the keys that match a pattern, that contain a certain expression. To do this, the &lt;code&gt;Cache&lt;/code&gt; facade is no longer used, but we use the &lt;code&gt;Redis&lt;/code&gt; one directly, which provides more advanced methods as the ones that exist when interacting directly with the Redis server. Under the Redis syntax, an asterisk &lt;code&gt;*&lt;/code&gt; means &lt;code&gt;all&lt;/code&gt;. So, for example, if we want to obtain all the keys, the pattern would simply be &lt;code&gt;*&lt;/code&gt;. Some &lt;em&gt;&lt;strong&gt;bad&lt;/strong&gt;&lt;/em&gt; examples would recommend to do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$found_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would return us an array with all the keys that were found. If we wanted to get the values we would have to loop through this array and get every value using the same &lt;code&gt;Redis&lt;/code&gt; facade, as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$found_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get the values&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$found_keys&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$found_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$found_key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Do something with the value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By this point you should have two questions: why is it bad to do this and why is &lt;code&gt;Redis&lt;/code&gt; used instead of &lt;code&gt;Cache&lt;/code&gt; to get the values. The latter is easier to answer, so I'll start there. Do you remember that I said that when using &lt;code&gt;Cache&lt;/code&gt; Laravel handles the prefix automatically? Well, when the keys are obtained using &lt;code&gt;Redis&lt;/code&gt;, these contain the prefix, so if we tried to find their value using &lt;code&gt;Cache&lt;/code&gt;, it wouldn't work, since Laravel adds the prefix, but it doesn't detect if it already exists, so the result would be a key with the prefix duplicated.&lt;/p&gt;

&lt;p&gt;Moving on to the other question, the issue with using the &lt;code&gt;keys&lt;/code&gt; method is that it blocks the execution of the code while it searches for the keys, why? Because it gets all the keys at once, and if there are a lot, well, let's wait for it to end. So what you're doing is using PHP to operate over Redis records, that is, you're using a programming language to search between all the records of a database, instead of using the database itself to do this process. That will clearly be slower, and even more if we remember that PHP isn't a language designed for high performance applications and large volumes of data. So, what's the right approach?&lt;/p&gt;

&lt;p&gt;In order to get Redis to do all the job instead of PHP, we have to use the &lt;code&gt;scan&lt;/code&gt; method, which as its name mentions, scans the records and uses a &lt;strong&gt;&lt;code&gt;cursor&lt;/code&gt;&lt;/strong&gt; to have a reference of the last position where it searched for and that way it goes little by little, incrementally, moving between the keys. In other words, instead of obtaining all the keys at once, it gets a few of them and keeps looking. The same example, using &lt;code&gt;scan&lt;/code&gt;, would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cache'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Store the connection in a variable so as not to open multiple connections&lt;/span&gt;
&lt;span class="nv"&gt;$prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'database.redis.options.prefix'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This is fundamental so as the scan method can loop through the records at least once&lt;/span&gt;
&lt;span class="nv"&gt;$pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Important to include the prefix&lt;/span&gt;

&lt;span class="nv"&gt;$found_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'match'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="c1"&gt;// Adjust according to the maximum size of keys needed&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nv"&gt;$found_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$found_keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cursor&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// The rule of the scan method is that it returns false once the cursor is 0&lt;/span&gt;

&lt;span class="c1"&gt;// Get the values&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$found_keys&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$found_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$found_key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Do something with the value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a bonus, let's say that we want to look for all the keys that start with &lt;code&gt;test&lt;/code&gt;. For this, the pattern would simply be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;test*"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's important to note the use of the asterisk because otherwise we would be searching for &lt;code&gt;test&lt;/code&gt; exactly, and if it wasn't clear already, we must never use the &lt;code&gt;keys&lt;/code&gt; method in production environments, we must always use &lt;code&gt;scan&lt;/code&gt;. You can, of course, use &lt;code&gt;keys&lt;/code&gt; locally, since it's easier to implement and you don't have much data nor a server on which the users depend, but when in production, the use of &lt;code&gt;scan&lt;/code&gt; is mandatory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delete the keys using the scan method
&lt;/h2&gt;

&lt;p&gt;Another common situation is that we need to search for the keys that match a pattern and then delete them. For this, the &lt;code&gt;del&lt;/code&gt; method is available, which allows us to delete several keys at once, that is, the direct result of the &lt;code&gt;scan&lt;/code&gt; method after each iteration. But here's a little detail, which took me hours to figure out at its time and this is my chance to say "You're welcome" so you don't lose your time too. For a reason unknown to me, the &lt;code&gt;del&lt;/code&gt; method doesn't work if the keys include the prefix, so it must be deleted. By adjusting the previous example to remove the keys on each iteration, we have the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cache'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'database.redis.options.prefix'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$found_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'match'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Delete the keys prefix (otherwise, they won't get deleted)&lt;/span&gt;
    &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Delete the keys found&lt;/span&gt;
        &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cursor&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Redis is a highly powerful in-memory database that can help us to implement caching mechanisms in our applications. By using it the right way, it can help to reduce response times by providing cached content instead of having to look for it in another database.&lt;/p&gt;

&lt;p&gt;I hope you find this article useful and if you have questions or want to share something, leave it in the comments :)&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>redis</category>
      <category>backend</category>
    </item>
    <item>
      <title>Transforming API requests and responses in Laravel 11 - The easy way</title>
      <dc:creator>Carlos Talavera</dc:creator>
      <pubDate>Tue, 14 May 2024 17:26:49 +0000</pubDate>
      <link>https://dev.to/charliet1802/transforming-api-requests-and-responses-in-laravel-11-the-easy-way-21i5</link>
      <guid>https://dev.to/charliet1802/transforming-api-requests-and-responses-in-laravel-11-the-easy-way-21i5</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;I've been working on an application using &lt;a href="https://nextjs.org" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; on the front-end and &lt;a href="https://laravel.com" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt; on the back-end as a traditional REST API. As you may know, snake_case is the naming convention for variable and function names in PHP, while camelCase is the naming convention in JavaScript. My database tables and columns use snake_case as well, so I stuck to that design.&lt;/p&gt;

&lt;h2&gt;
  
  
  A first approach: Laravel resources
&lt;/h2&gt;

&lt;p&gt;I was already using &lt;a href="https://laravel.com/docs/11.x/eloquent-resources" rel="noopener noreferrer"&gt;Laravel resources&lt;/a&gt; to return clean API responses. For each API response we can specify each key using camelCase. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JsonResource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Transform the resource into an array.
     *
     * @return array&amp;lt;string, mixed&amp;gt;
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'stock'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'category'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'imageUrl'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'salesCount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sales_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this is valid, it could be highly demanding for us to format every API response to camelCase, and even more when we have &lt;a href="https://laravel.com/docs/11.x/eloquent-relationships" rel="noopener noreferrer"&gt;Eloquent relationships &lt;/a&gt; that we have to format too. We could also say "Well, let's create a &lt;code&gt;BaseResource&lt;/code&gt; that extends the &lt;code&gt;JsonResouce&lt;/code&gt; class, which formats everything to camelCase, and then have every resource class extend from there". It would be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JsonResource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Convert array keys from snake_case to camelCase.
     *
     * @param array $array
     * @return array
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toCamelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$camelCaseArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$array&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Here we use the Str::camel() method from Laravel&lt;/span&gt;
            &lt;span class="nv"&gt;$camelKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;camel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Recursively convert nested arrays&lt;/span&gt;
            &lt;span class="nv"&gt;$camelCaseArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$camelKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toCamelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$camelCaseArray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toCamelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Note the need for a recursive approach to deal with nested arrays).&lt;/p&gt;

&lt;p&gt;That way, we have two scenarios: one where we simply extend from that class to display the visible fields from a table as they are, i. e., without any formatting for relationships or hidden fields excluded from the API response; and the second scenario where we do want to format our API response. The first case would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseResource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// No need for anything in here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second case would be the &lt;code&gt;ProductResource&lt;/code&gt; class already showed, but with key differences: it preserves snake_case, extends the &lt;code&gt;BaseResource&lt;/code&gt; class and implements the &lt;code&gt;toCamelCase()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseResource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Transform the resource into an array.
     *
     * @return array&amp;lt;string, mixed&amp;gt;
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toCamelCase&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'stock'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'category'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'image_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'sales_count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sales_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That seems nice too. So, what's the catch? To me, there are two inconveniences: on one side, if we already have several resources we would have to change them all to extend &lt;code&gt;BaseResource&lt;/code&gt; and use &lt;code&gt;toCamelCase()&lt;/code&gt; where it applies; and on the other side, we should be really careful all the time while creating resources so they extend &lt;code&gt;BaseResource&lt;/code&gt; and not &lt;code&gt;JsonResource&lt;/code&gt;, and apply the conversion method if needed. We could modify the &lt;a href="https://laravel.com/docs/11.x/artisan" rel="noopener noreferrer"&gt;Artisan&lt;/a&gt; command for creating resources so they extend &lt;code&gt;BaseResource&lt;/code&gt; instead of &lt;code&gt;JsonResource&lt;/code&gt;. But all of that seems like too much work to me.&lt;/p&gt;

&lt;p&gt;There's also one more thing, what if our API expects requests using snake_case? We could format API responses to camelCase, but we should format them back again to snake_case from our Next application. While Next supports &lt;a href="https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering" rel="noopener noreferrer"&gt;server-side rendering&lt;/a&gt;, it doesn't sound like a good option to leave all that work to what is supposed to be the front-end. So, what can we do?&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: Laravel middlewares
&lt;/h2&gt;

&lt;p&gt;To me, a middleware is basically something that intercepts traffic on a higher level and does stuff with it (I know it's vague, but why complicate things?). With higher level I mean that, unlike a proxy, a middleware does not work with a raw HTTP protocol, but does something with HTTP information already parsed.&lt;/p&gt;

&lt;p&gt;What we want to achieve is to use an input middleware and an output middleware. One for "what we are receiving" and one for "what we are replying". We'll call the input one &lt;code&gt;TransformApiRequest&lt;/code&gt;, so it handles the camelCase to snake_case conversion. The output one will be called &lt;code&gt;TransformApiResponse&lt;/code&gt;, so it handles the snake_case to camelCase conversion. This way we keep our naming conventions within our Laravel application and just transform the information to the way we need it to be. Besides, all that transformation logic lives in the same place, making it easier to maintain.&lt;/p&gt;

&lt;p&gt;Using Laravel 11, we create these middlewares as follows:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;php artisan make:middleware TransformApiRequest&lt;/code&gt;&lt;br&gt;
&lt;code&gt;php artisan make:middleware TransformApiResponse&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I'm not using &lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, so I'm not utilizing &lt;a href="https://laravel.com/docs/11.x/sail" rel="noopener noreferrer"&gt;Laravel Sail&lt;/a&gt;. However, if you're using it, simply replace &lt;code&gt;php&lt;/code&gt; with &lt;code&gt;sail&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TransformApiRequest&lt;/code&gt; middleware would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransformApiRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Transform keys of requests that are not GET to snake_case&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$transformedInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transformKeysToSnakeCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$transformedInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Transform keys of an array to snake_case.
     *
     * @param  array  $input
     * @return array
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;transformKeysToSnakeCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Here we use the Str::snake() method from Laravel&lt;/span&gt;
            &lt;span class="nv"&gt;$snakeKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$snakeKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transformKeysToSnakeCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, I didn't want to convert GET requests with query parameters to snake_case because some of the parameters needed to stay in camelCase since I use (with no promotion intended) &lt;a href="https://github.com/abbasudo/laravel-purity" rel="noopener noreferrer"&gt;Laravel Purity&lt;/a&gt; for filtering. So, when it comes to Eloquent relationships, camelCase is the way to go. The other logic is self-explanatory.&lt;/p&gt;

&lt;p&gt;As for the &lt;code&gt;TransformApiResponse&lt;/code&gt; middleware, it's just a little bit more complex because we're not dealing with incoming requests, but with responses, so we need a way to identify when we need to format the response to camelCase. A clear condition is that the &lt;code&gt;Content-Type&lt;/code&gt; header of our response must be &lt;code&gt;application/json&lt;/code&gt;. But maybe we only want to transform successful responses, which makes the most sense to me, because, why would we have a complex error JSON response with keys long enough to be different in snake_case and camelCase? So let's consider that condition too. This middleware would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransformApiResponse&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Transform keys of successful JSON responses to camelCase&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isSuccessful&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContent&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$transformedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transformKeysToCamelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$transformedData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Transform keys of an array to camelCase.
     *
     * @param  array  $data
     * @return array
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;transformKeysToCamelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Here we use the Str::camel() method from Laravel&lt;/span&gt;
            &lt;span class="nv"&gt;$camelKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;camel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$camelKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transformKeysToCamelCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$response-&amp;gt;isSuccessful()&lt;/code&gt; verifies that the response has a status code between 200 and 299. The other logic is self-explanatory.&lt;/p&gt;

&lt;p&gt;Now we need to add these middlewares within our application. In Laravel 11, we go to &lt;code&gt;bootstrap/app.php&lt;/code&gt; and locate the &lt;code&gt;withMiddleware()&lt;/code&gt; method. As we're working on an API, we need to append these new middlewares to the &lt;code&gt;api&lt;/code&gt; middleware group as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Middleware&lt;/span&gt; &lt;span class="nv"&gt;$middleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Transform keys of requests that are not GET to snake_case&lt;/span&gt;
        &lt;span class="c1"&gt;// and keys of successful JSON responses to camelCase&lt;/span&gt;
        &lt;span class="nv"&gt;$middleware&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;appendToGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'api'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;TransformApiRequest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;TransformApiResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. With this approach, now we can keep developing using snake_case in Laravel and camelCase in Next.js seamlessly :)&lt;/p&gt;

&lt;h2&gt;
  
  
  "Drawbacks"
&lt;/h2&gt;

&lt;p&gt;While it is true that the use of middlewares has an impact on performance, it is also true that we should keep API responses and requests as clean and short as possible. Thus, transforming information from snake_case to camelCase and vice versa should not be the culprit of a slow application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Laravel is a great framework with a wide set of built-in features that make our life easier, but sometimes we have to decide which is the best way to go to reduce complexity and achieve our goals. In this case, it seemed that using Laravel resources would be a good option at first, but then, using custom middlewares turned out to be more sustainable and easier to implement.&lt;/p&gt;

&lt;p&gt;This is my first article, so any feedback is well appreciated. I hope you liked it and found it useful :D&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
