<?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: Jorge Peraza</title>
    <description>The latest articles on DEV Community by Jorge Peraza (@yorchperaza).</description>
    <link>https://dev.to/yorchperaza</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F523500%2F0e1b9593-4b5e-4f86-bf86-72a2a8ac5c0d.jpg</url>
      <title>DEV Community: Jorge Peraza</title>
      <link>https://dev.to/yorchperaza</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yorchperaza"/>
    <language>en</language>
    <item>
      <title>Stop Prompting, Start Pairing: How to Actually Get Better at Code by Working With AI Agents</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Wed, 16 Sep 2026 23:45:44 +0000</pubDate>
      <link>https://dev.to/yorchperaza/stop-prompting-start-pairing-how-to-actually-get-better-at-code-by-working-with-ai-agents-57ld</link>
      <guid>https://dev.to/yorchperaza/stop-prompting-start-pairing-how-to-actually-get-better-at-code-by-working-with-ai-agents-57ld</guid>
      <description>&lt;p&gt;I've been writing code for more than 20 years: PHP, Drupal, TypeScript, cloud infrastructure, and now LLM serving for an agentic IDE I'm building. Over the last year of working with coding agents every day, I've seen developers split into two groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developers who got sharper.&lt;/strong&gt; They understand more languages, make better architecture decisions, and review code faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers who got rusty.&lt;/strong&gt; They ship more code than ever, but they couldn't explain half of it without the agent open.
The tool isn't the difference. Both groups use the same agents. The difference is &lt;strong&gt;what they delegate&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The trap: delegating the thinking
&lt;/h2&gt;

&lt;p&gt;The default loop looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt → accept → run → "works" → ship
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It feels productive, and in the short term it is. But this loop trains you in only one thing: writing prompts. You get output without gaining skill. Six months later, you're faster at producing code and slower at understanding it.&lt;/p&gt;

&lt;p&gt;The fix isn't to stop using agents. It's to change the relationship. Treat the agent as three things at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A senior pair programmer&lt;/strong&gt; who challenges your decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rubber duck that talks back&lt;/strong&gt; when you need to think out loud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A student&lt;/strong&gt; you have to review and correct
Here are the practices that made me better, not just faster.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Design first, and make the agent argue with you
&lt;/h2&gt;

&lt;p&gt;Before any code, ask for options instead of an implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to add multi-tenant support to this service.
Don't write code yet. Give me 3 architectural approaches
(e.g., shared schema, schema-per-tenant, database-per-tenant),
with trade-offs in cost, isolation, migrations, and operational
complexity. Then ask me which one I'd pick and why.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;strong&gt;you&lt;/strong&gt; choose, and you write down why. Now you're practicing the most valuable skill in the job, making architecture decisions under trade-offs, with an expert opponent available 24/7.&lt;/p&gt;

&lt;p&gt;Bonus: save that reasoning as an ADR (Architecture Decision Record). Future you, and future agents, will thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. "Attempt first" for anything you want to learn
&lt;/h2&gt;

&lt;p&gt;For code you just need done, let the agent write it. For code in an area you want to master, write it yourself first, then ask for a review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here's my implementation. Review it like a strict senior engineer.
Point out bugs, non-idiomatic code, performance issues, and
edge cases I missed. Don't rewrite it — explain each issue
and let me fix it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line "don't rewrite it" matters. If the agent fixes the issue, you learn nothing. If you fix it, you remember it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The "explain it back" rule
&lt;/h2&gt;

&lt;p&gt;My rule before merging any agent-generated code: &lt;strong&gt;I must be able to explain every line.&lt;/strong&gt; If I can't, that's not a reason to trust it. It's a gap in what I know.&lt;/p&gt;

&lt;p&gt;When I hit a line I don't fully understand, I flip the roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Don't explain this code to me. Quiz me on it.
Ask me 5 questions about what this function does,
why it's written this way, and what breaks if we change it.
Tell me which answers are wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Being quizzed exposes gaps much faster than reading an explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Learn new languages by porting code you already know
&lt;/h2&gt;

&lt;p&gt;The fastest way I've found to learn a new language with an agent is to &lt;strong&gt;port something you already understand deeply&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Take a small module you wrote in your main language (for me, PHP) and rewrite it yourself in the target language (Go, Rust, whatever). Then ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I ported this PHP class to Go. Compare both versions.
Where is my Go code "PHP written in Go syntax"?
Show me the idiomatic Go approach for each case and explain
the language concept behind it (interfaces, error handling,
ownership, concurrency, etc.).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because you already know the domain logic, all of your attention goes to the language itself: its idioms, its type system, its way of handling errors. You learn the language from your own mistakes instead of tutorial examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Always ask for the "why" and a source you can verify
&lt;/h2&gt;

&lt;p&gt;Agents are confident even when they're wrong. Turn that into a learning habit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Why is this the correct approach? Point me to the relevant
documentation section, RFC, or language spec so I can verify it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;strong&gt;actually open the source&lt;/strong&gt;. Half the time you'll learn something the agent didn't mention. The other half, you'll catch it being wrong, and that teaches you even more.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Break things on purpose
&lt;/h2&gt;

&lt;p&gt;This is my favorite one. Use the agent to create debugging exercises from your own codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Introduce one subtle bug into this module — something that
passes a quick review but fails in production (race condition,
off-by-one, timezone, null handling). Don't tell me where.
Give me only the symptom a user would report.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hunt it down without help. It's like a debugging gym built from your own real code. You can also have the agent write failing tests and make them pass yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Architecture katas: let the agent be the chaotic stakeholder
&lt;/h2&gt;

&lt;p&gt;Designs look great until the requirements change. So simulate the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here's my design for an order system. Act as a product manager
who changes requirements every round. Give me one new requirement
at a time (multi-currency, partial refunds, 10x traffic,
GDPR deletion...). After each one, ask me what changes in my design.
Tell me when my design starts to break.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After five rounds, you'll know where your design is fragile, and why patterns like event sourcing, CQRS, or hexagonal architecture exist. You'll understand them from experience, not from a blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Review the agent like you'd review a junior, and keep a log
&lt;/h2&gt;

&lt;p&gt;Treat every agent PR as a pull request from a talented junior developer: read it, question it, request changes.&lt;/p&gt;

&lt;p&gt;Then keep a simple &lt;strong&gt;"agent mistakes log"&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;Why it was wrong&lt;/th&gt;
&lt;th&gt;Pattern to remember&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;09/12&lt;/td&gt;
&lt;td&gt;N+1 queries in listing&lt;/td&gt;
&lt;td&gt;Lazy loading inside loop&lt;/td&gt;
&lt;td&gt;Eager-load relations in list views&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;09/14&lt;/td&gt;
&lt;td&gt;Swallowed exception&lt;/td&gt;
&lt;td&gt;Empty catch block&lt;/td&gt;
&lt;td&gt;Never catch without logging or rethrowing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After a month, this log becomes your personal list of anti-patterns. The agent's mistakes turn into your expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Write your standards down, because it forces you to know them
&lt;/h2&gt;

&lt;p&gt;Most agent tools support a project rules file (for example &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt;, or similar) where you describe conventions, architecture, and boundaries.&lt;/p&gt;

&lt;p&gt;Writing that file is a surprisingly hard exercise. You have to spell out things you've only ever "felt":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why is the domain layer separate from the infrastructure layer?&lt;/li&gt;
&lt;li&gt;When do we use a queue versus a synchronous call?&lt;/li&gt;
&lt;li&gt;What does "done" mean in this codebase?
If you can't write your architecture down clearly, you don't understand it clearly yet. The agent just gave you a reason to find out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple weekly routine
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daily:&lt;/strong&gt; Apply the "explain it back" rule to every merged PR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twice a week:&lt;/strong&gt; Write one feature yourself first, then get an agent review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly:&lt;/strong&gt; Do one bug hunt (practice 6) or one architecture kata (practice 7).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly:&lt;/strong&gt; Review your mistakes log and update your rules file.
It adds maybe 2 to 3 hours a week. In return, you grow as an engineer instead of just getting faster at typing prompts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to know if you're learning or just outsourcing
&lt;/h2&gt;

&lt;p&gt;Ask yourself honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Could I rebuild last week's feature without the agent, even if it took longer?&lt;/li&gt;
&lt;li&gt;Do I catch the agent's mistakes more often than I did three months ago?&lt;/li&gt;
&lt;li&gt;Can I explain the trade-offs behind our current architecture?&lt;/li&gt;
&lt;li&gt;Am I comfortable reading code in a language I didn't know six months ago?
If the answers are "yes," the agent is making you better. If they're "no," you're renting skill instead of building it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Improve together
&lt;/h2&gt;

&lt;p&gt;Agents aren't going away, and they'll keep getting better. The developers who thrive won't be the ones who write the best prompts. They'll be the ones who &lt;strong&gt;use every interaction as a chance to learn&lt;/strong&gt;, and who can still think clearly when the agent is wrong.&lt;/p&gt;

&lt;p&gt;Don't just give the agent instructions. Grow alongside it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's one thing an AI agent taught you that actually stuck? Tell me in the comments. I'd love to build a list of learning techniques together.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>career</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Your AI Coding Agent Shouldn't Live on Your Laptop</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Mon, 14 Sep 2026 00:47:19 +0000</pubDate>
      <link>https://dev.to/yorchperaza/your-ai-coding-agent-shouldnt-live-on-your-laptop-166j</link>
      <guid>https://dev.to/yorchperaza/your-ai-coding-agent-shouldnt-live-on-your-laptop-166j</guid>
      <description>&lt;p&gt;Every AI coding tool I have used makes the same assumption: the code is on the laptop you are sitting at.&lt;/p&gt;

&lt;p&gt;That is true for maybe half of what an engineering team actually does. The rest happens on a dev box, in CI, on a staging environment, or on a build machine that has the toolchain your laptop does not.&lt;/p&gt;

&lt;p&gt;We just shipped the MonkeysCode Code Agent, a desktop app for running coding agents across projects without an editor in the way. The most interesting part of it is not the agent loop. It is where the agent is allowed to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four execution backends
&lt;/h2&gt;

&lt;p&gt;The agent can execute against any of these, and you pick per workspace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local.&lt;/strong&gt; Your machine. Full capability, including semantic search against a local index and interactive shell sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSH.&lt;/strong&gt; A remote development box. Every command is translated and executed there, not here. This is the one nobody else offers, and it is the one I use most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloud.&lt;/strong&gt; A managed container workspace, sandboxed by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remote control.&lt;/strong&gt; A peer machine over WebSocket, running a remote host agent. A build server, a test rig, a colleague's environment.&lt;/p&gt;

&lt;p&gt;Each backend declares its real capabilities honestly. Semantic search is not available over SSH, so the agent's system prompt says so and the UI adapts. It is not allowed to attempt something that will fail and then improvise around the error.&lt;/p&gt;

&lt;p&gt;That last detail matters more than it sounds. A lot of agent frustration comes from the model confidently trying something the environment cannot do, failing, and then trying three more things. Telling it the truth up front is cheaper than letting it discover the constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like for DevOps
&lt;/h2&gt;

&lt;p&gt;Here is the part I think is genuinely underexplored in this category.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI that fixes itself
&lt;/h3&gt;

&lt;p&gt;The CLI runs the same engine headless, with structured output and real exit codes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Attempt automated fix&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;failure()&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;mcode -p "The test suite failed. Read the output, find the cause, fix it." \&lt;/span&gt;
          &lt;span class="s"&gt;--output json \&lt;/span&gt;
          &lt;span class="s"&gt;--permission-mode accept-edits &amp;gt; result.json&lt;/span&gt;
    &lt;span class="s"&gt;echo "exit=$?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because every run is test-gated, a change that does not make the suite green never gets applied. The agent either produces a working fix or it produces nothing. There is no middle state where it commits something plausible and moves on.&lt;/p&gt;

&lt;p&gt;That is what makes this usable unattended. Not the model quality, the guardrail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled maintenance
&lt;/h3&gt;

&lt;p&gt;Background and scheduled runs mean the boring recurring work can happen without a human starting it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every Monday 06:00
  → Update dependencies, run the suite, open a PR if green
Every night
  → Generate tests for files changed in the last 24h
Every deploy
  → Check the changelog matches what actually shipped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one produces a signed record and a diff you review before merging. It is not autonomous merging, it is autonomous preparation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agents on the machine that has the toolchain
&lt;/h3&gt;

&lt;p&gt;This is the SSH case and it comes up constantly.&lt;/p&gt;

&lt;p&gt;Your laptop is macOS on ARM. Production is Linux on x86. The build needs a GPU you do not have, or a database that only exists on the dev box, or a proprietary compiler your security team will not license per-seat.&lt;/p&gt;

&lt;p&gt;Point the agent at the machine where those things live. It reads the real filesystem, runs the real build, sees the real errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcode &lt;span class="nt"&gt;--backend&lt;/span&gt; ssh &lt;span class="nt"&gt;--host&lt;/span&gt; devbox-3 &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"The nightly build is failing on the arm64 target. Diagnose it."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No syncing, no containerising your dev environment, no reproducing production locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Many agents, many projects
&lt;/h3&gt;

&lt;p&gt;The desktop app orchestrates parallel runs across independent projects, each in an isolated workspace. A mission board shows what every agent is doing, an inbox collects the ones waiting on a decision, and you review a signed diff and test report before anything merges.&lt;/p&gt;

&lt;p&gt;For anyone maintaining several services, this is the difference between an agent being a tool you use and an agent being work that happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guardrails, because autonomy without them is a liability
&lt;/h2&gt;

&lt;p&gt;Four permission modes, and you switch with a keystroke.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Reads&lt;/th&gt;
&lt;th&gt;Edits&lt;/th&gt;
&lt;th&gt;Runs commands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plan&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ask&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;on approval&lt;/td&gt;
&lt;td&gt;on approval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accept edits&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;td&gt;on approval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that edit permission and command permission are deliberately separate. In practice people trust an agent to modify a file long before they trust it to run something in their shell. Bundling those into one autonomy slider was a mistake we made early and corrected.&lt;/p&gt;

&lt;p&gt;Auto mode is only available inside a sandbox. Not as a policy, as an enforced constraint. Seatbelt on macOS, bubblewrap on Linux, AppContainer on Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two engineering details worth stealing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  We did not build undo on git stash
&lt;/h3&gt;

&lt;p&gt;Every file the agent touches is snapshotted before its first mutation, so you can revert one file or an entire run.&lt;/p&gt;

&lt;p&gt;The obvious implementation is &lt;code&gt;git stash push -u&lt;/code&gt;. One command, already there, works.&lt;/p&gt;

&lt;p&gt;It also destroys your own uncommitted work.&lt;/p&gt;

&lt;p&gt;Think about the actual scenario. You have half-finished changes in your tree. You ask an agent to do something unrelated. You do not like the result and you revert. With git stash, your half-finished changes went with it.&lt;/p&gt;

&lt;p&gt;So we built a separate snapshot store. Pre-mutation capture per file, selective revert, workspace-scoped so it cannot reach into another project, and honest about limits: a recursive directory delete is flagged as unrevertable rather than silently skipped.&lt;/p&gt;

&lt;p&gt;More work than one git command. But an undo feature that occasionally destroys your work is not an undo feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnostics report only what the agent broke
&lt;/h3&gt;

&lt;p&gt;After edits, the agent runs your project's type-checker or linter.&lt;/p&gt;

&lt;p&gt;The important part is that it captures a &lt;strong&gt;baseline at run start&lt;/strong&gt; and reports only what is new. If your codebase already has 400 warnings, you do not get 400 warnings. You get the ones it just introduced.&lt;/p&gt;

&lt;p&gt;Anyone who has introduced a linter to a legacy codebase knows why this matters. Signal drowns in noise, people stop reading it, and the tool stops working. A verification step that produces noise is the same as no verification step.&lt;/p&gt;

&lt;p&gt;It also detects the right checker per language, scopes to affected packages in a monorepo rather than checking everything, and retries once on transient failures so a mid-build state does not report a false problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research that outlives the session
&lt;/h2&gt;

&lt;p&gt;One more thing that does not exist elsewhere as far as I know.&lt;/p&gt;

&lt;p&gt;Some work is not a task. It is a question you chase across days. A performance regression nobody can reproduce. A migration you are scoping. A bug that only appears under load. That is a dozen runs across a week, and by Thursday you have forgotten what you ruled out on Monday.&lt;/p&gt;

&lt;p&gt;Investigations are long-lived threads scoped to a project. Runs link to them, findings accumulate, and the output is real markdown under &lt;code&gt;.monkeyscode/docs/&lt;/code&gt; in your repository. Git-tracked. Editable in any editor. Reviewable in a pull request.&lt;/p&gt;

&lt;p&gt;Not locked in an application database that dies with the tool. If your work lives inside a vendor's storage, you do not own it.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the model
&lt;/h2&gt;

&lt;p&gt;The agent runs on Capuchin, which we train and serve ourselves, or on Claude, Gemini, ChatGPT, or a model on your own hardware. Switching is a setting.&lt;/p&gt;

&lt;p&gt;Capuchin Reason scores 62.1% on SWE-bench Pro. Claude Opus, the current leader, scores 64.3%. We are 2.2 points behind, measured on our own production endpoint at the same quantization and context configuration we actually serve, with the methodology published.&lt;/p&gt;

&lt;p&gt;The number I find more informative: in production, 92.7% of requests run on Capuchin rather than on a frontier model, even though every user can switch with one setting. For most daily work the 2.2 points are not something you feel.&lt;/p&gt;

&lt;p&gt;Owning the model is also why we can support the ones we do not own. Capuchin is cheap enough for us to serve that we can include frontier allowances in the plan and support local models we earn nothing from.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is not finished
&lt;/h2&gt;

&lt;p&gt;Semantic search only works on the local backend. Over SSH or against a cloud container you get grep and file operations, not vector search. The agent is told this explicitly rather than being allowed to try and fail, but it is a real limitation.&lt;/p&gt;

&lt;p&gt;We have not published a cost-per-task figure. Cursor has. We should, and we will, once the measurement is standardised.&lt;/p&gt;

&lt;p&gt;The CLI is shipping shortly rather than shipped.&lt;/p&gt;

&lt;p&gt;We are pre-1.0 and say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The Code Agent and the IDE are both included in every plan, including the free tier with your own API keys. Thirty days of unlimited Capuchin, no credit card.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://monkeyscode.com/code-agent" rel="noopener noreferrer"&gt;monkeyscode.com/code-agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it, I would rather hear what broke than get a signup. Comments are open and I read all of them, particularly if you have opinions about the SSH backend, which is the part I am least sure we have got right.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ai</category>
      <category>productivity</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Meet Capuchin AI: Fast, Reasoning, and Not the Only Option</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Wed, 29 Jul 2026 01:46:28 +0000</pubDate>
      <link>https://dev.to/yorchperaza/meet-capuchin-ai-fast-reasoning-and-not-the-only-option-10km</link>
      <guid>https://dev.to/yorchperaza/meet-capuchin-ai-fast-reasoning-and-not-the-only-option-10km</guid>
      <description>&lt;p&gt;Capuchin is the AI that powers MonkeysCode by default. This post is about what it actually does, how it's served, and, since we've gotten this question a lot, what it &lt;em&gt;isn't&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two modes, one model
&lt;/h2&gt;

&lt;p&gt;Coding tasks aren't uniform. Autocomplete and small edits need to feel instant. Planning a multi-file refactor or chasing a gnarly bug benefits from actually thinking it through. Most tools pick one tradeoff and apply it everywhere. Capuchin doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capuchin Flash.&lt;/strong&gt; Thinking off, sub-second responses. This is the 80% of coding work: completions, small edits, quick questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capuchin Reason.&lt;/strong&gt; Step-by-step reasoning, budgeted so "thinking" never turns into "spinning." This is the 20% that's actually hard: multi-file plans, debugging, tradeoffs.
MonkeysCode routes between them automatically based on the task, or you can pin one yourself. The point isn't "slow but smart" vs "fast but dumb." It's picking the right tool per step, inside a single session.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why we can afford to include unlimited usage
&lt;/h2&gt;

&lt;p&gt;The honest answer is efficiency. Capuchin is built to activate only a fraction of its total capacity per token. The architecture spends its weights on software-engineering tasks specifically, rather than spreading capacity across everything a general chat model tries to do. That's what makes it both sharper on real coding work and cheap enough to serve that we can bundle generous usage into a subscription instead of metering every token.&lt;/p&gt;

&lt;p&gt;Practically, that shows up as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speculative decoding and caching&lt;/strong&gt; keep response times low even under load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A fair-use ceiling&lt;/strong&gt;, not literal infinity. "Unlimited" is generous, not unbounded, so the economics stay sane for everyone
## Built to use tools, not just talk about them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Capuchin is tuned specifically for agentic work: it calls your terminal, git, test runner, browser, and MCP servers and uses the results accurately across long sessions, the kind of 20-30-tool-call runs that real refactors actually take, not a single-shot completion. It plans before it acts, executes across files in parallel, and runs your tests before handing back. If something fails, it sees the failure and fixes it rather than reporting success prematurely.&lt;/p&gt;

&lt;p&gt;It's also polyglot by design, strong across Python, JavaScript/TypeScript, Go, Rust, Java, and more, because most of us aren't working in a single language, and a model that's only good at one is a model you'll outgrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is &lt;em&gt;not&lt;/em&gt;: a local model
&lt;/h2&gt;

&lt;p&gt;This is worth being precise about, because we've seen the confusion. &lt;strong&gt;Capuchin is a managed cloud service&lt;/strong&gt;, the same delivery model as Claude, ChatGPT, or Codex. It is not something you download and run on your laptop.&lt;/p&gt;

&lt;p&gt;If you need fully offline or air-gapped work, that's a real, fully supported path in MonkeysCode. It's just a different one: point the IDE at &lt;strong&gt;your own model&lt;/strong&gt;, local (Ollama, llama.cpp, vLLM) or on your own server. Capuchin is the managed default; your own model is the offline path. Two clean, non-overlapping stories, not one confused one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it gets better
&lt;/h2&gt;

&lt;p&gt;We start from a strong open foundation and specialize it hard for real software-engineering work inside MonkeysCode, rather than training something narrow from scratch. Improvement after that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opt-in only.&lt;/strong&gt; Free, Pro, and Team users choose whether usage signals contribute to improving the model. The default is off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never from enterprise, self-hosted, or air-gapped installs.&lt;/strong&gt; No exceptions, regardless of any general opt-in setting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal over source.&lt;/strong&gt; Where opt-in is given, we prefer derived signals (was the edit kept? did tests pass?) over retaining raw code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gated releases.&lt;/strong&gt; Every new checkpoint has to beat the current one on our internal benchmark before it ships. No regressions allowed through.
## Try it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Capuchin is included with unlimited usage on every paid MonkeysCode plan, and free to try for a month during our founding-user promo.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://monkeyscode.com/promo" rel="noopener noreferrer"&gt;monkeyscode.com/promo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've got questions about the Flash/Reason routing, the fair-use design, or where the local/managed line actually sits, ask away in the comments. Happy to get into the weeds.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Authentication with MonkeysLegion 2.0 + Next.js / React</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Fri, 08 May 2026 02:58:20 +0000</pubDate>
      <link>https://dev.to/yorchperaza/authentication-with-monkeyslegion-20-nextjs-react-2en4</link>
      <guid>https://dev.to/yorchperaza/authentication-with-monkeyslegion-20-nextjs-react-2en4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A complete guide to implementing JWT-based registration, login, session persistence, and token refresh using the MonkeysLegion v2 API with a Next.js (or React) frontend.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;API Endpoints Overview&lt;/li&gt;
&lt;li&gt;Backend Setup (MonkeysLegion)&lt;/li&gt;
&lt;li&gt;Frontend: API Client&lt;/li&gt;
&lt;li&gt;Frontend: Auth Context Provider&lt;/li&gt;
&lt;li&gt;Frontend: Register Page&lt;/li&gt;
&lt;li&gt;Frontend: Login Page&lt;/li&gt;
&lt;li&gt;Frontend: Protected Routes&lt;/li&gt;
&lt;li&gt;Token Refresh Flow&lt;/li&gt;
&lt;li&gt;Common Issues &amp;amp; Solutions&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  API Endpoints Overview
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion v2 exposes the following auth endpoints under the &lt;code&gt;/api/v2/auth&lt;/code&gt; prefix:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/register&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Create a new user + company&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Authenticate and get JWT tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/refresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Exchange refresh token for new access token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/me&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Get authenticated user profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/logout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Invalidate tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/forgot-password&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Request password reset&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Response Shapes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Login Response&lt;/strong&gt; — &lt;code&gt;POST /auth/login&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1Q..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1Q..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Register Response&lt;/strong&gt; — &lt;code&gt;POST /auth/register&lt;/code&gt; (HTTP 201)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Registration successful"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Inc."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1Q..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1Q..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Me Response&lt;/strong&gt; — &lt;code&gt;GET /auth/me&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UTC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"two_factor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-08T01:25:23+00:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Backend Setup (MonkeysLegion)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install the Framework
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require monkeyscloud/monkeyslegion:^2.0.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Version &lt;code&gt;2.0.8+&lt;/code&gt; is required. Earlier versions have a bug in &lt;code&gt;DatabaseUserProvider&lt;/code&gt; where typed properties (&lt;code&gt;DateTimeImmutable&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;) throw &lt;code&gt;TypeError&lt;/code&gt; during hydration from raw PDO strings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Entity — &lt;code&gt;app/Entity/User.php&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Entity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MonkeysLegion\Auth\Contract\AuthenticatableInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;MonkeysLegion\Query\Attribute\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Timestamps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Fillable&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="na"&gt;#[Entity(table: 'users')]&lt;/span&gt;
&lt;span class="na"&gt;#[Timestamps]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;AuthenticatableInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Id]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'string', length: 255, unique: true)]&lt;/span&gt;
    &lt;span class="na"&gt;#[Fillable]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'string', length: 255)]&lt;/span&gt;
    &lt;span class="na"&gt;#[Fillable]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$full_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'string', length: 255)]&lt;/span&gt;
    &lt;span class="na"&gt;#[Hidden]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$password_hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'integer', default: 1)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$token_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'datetime')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$created_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'datetime')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$updated_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ── AuthenticatableInterface ──&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;getAuthIdentifier&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&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;getAuthPassword&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&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="n"&gt;password_hash&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;h3&gt;
  
  
  3. Controller — &lt;code&gt;app/Controller/Api/AuthController.php&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller\Api&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Service\AuthService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;MonkeysLegion\Http\Attribute\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Throttle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Authenticated&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MonkeysLegion\Http\Message\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Psr\Http\Message\ServerRequestInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Prefix('/api/v2/auth')]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthController&lt;/span&gt;
&lt;span class="p"&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;AuthService&lt;/span&gt; &lt;span class="nv"&gt;$auth&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="na"&gt;#[Route('POST', '/login', name: 'auth.login')]&lt;/span&gt;
    &lt;span class="na"&gt;#[Throttle(max: 5, per: 60)]&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;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ServerRequestInterface&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;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$body&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="n"&gt;string&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;getBody&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="o"&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;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&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="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&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="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Missing credentials'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;400&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&amp;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;$body&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="nv"&gt;$body&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="k"&gt;if&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="kc"&gt;null&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="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Invalid credentials'&lt;/span&gt;&lt;span class="p"&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;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'data'&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;'access_token'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'access_token'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'refresh_token'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'token_type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Bearer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'expires_in'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'expires_in'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'user'&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;'id'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&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;'email'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&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;'full_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;full_name&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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('POST', '/register', name: 'auth.register')]&lt;/span&gt;
    &lt;span class="na"&gt;#[Throttle(max: 3, per: 60)]&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;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ServerRequestInterface&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;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$body&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="n"&gt;string&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;getBody&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="o"&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;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&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="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&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="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&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="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Missing required fields'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Check for duplicate email&lt;/span&gt;
        &lt;span class="nv"&gt;$existing&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="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$existing&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="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'error'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Validation failed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'details'&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;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Email already registered'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;422&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&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;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'data'&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;'message'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Registration successful'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'user'&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;'id'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&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;'email'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&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;'full_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'access_token'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'access_token'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'refresh_token'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'token_type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Bearer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'expires_in'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'expires_in'&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="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('GET', '/me', name: 'auth.me')]&lt;/span&gt;
    &lt;span class="na"&gt;#[Authenticated]&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;me&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ServerRequestInterface&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;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// NOTE: The attribute is 'auth.user', NOT 'user'&lt;/span&gt;
        &lt;span class="nv"&gt;$user&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;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'auth.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;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'data'&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;'id'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&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;'email'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&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;'full_name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'status'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'created_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always use &lt;code&gt;$request-&amp;gt;getAttribute('auth.user')&lt;/code&gt; — the &lt;code&gt;AuthenticationMiddleware&lt;/code&gt; sets the attribute as &lt;code&gt;auth.user&lt;/code&gt;, not &lt;code&gt;user&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Frontend: API Client
&lt;/h2&gt;

&lt;p&gt;Create a reusable API client that handles JWT tokens, automatic refresh, and typed requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/lib/api.ts&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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;API_URL&lt;/span&gt; &lt;span class="o"&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_API_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8088&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ApiOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApiClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&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="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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&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;async&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&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;authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getToken&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&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="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;method&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="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="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;authToken&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="p"&gt;:&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;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Auto-refresh on 401&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;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refreshed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refreshToken&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;refreshed&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getToken&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearTokens&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&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;err&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request failed&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;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;refresh&lt;/span&gt; &lt;span class="o"&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="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/auth/refresh`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&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="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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refresh&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;data&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&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="kc"&gt;true&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="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;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;clearTokens&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="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_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;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&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="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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ── Auth Methods ──────────────────────────────────────────&lt;/span&gt;
  &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="o"&gt;&amp;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;/api/v2/auth/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="o"&gt;&amp;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;/api/v2/auth/register&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;companyName&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApiClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Frontend: Auth Context Provider
&lt;/h2&gt;

&lt;p&gt;Wrap your app with &lt;code&gt;AuthProvider&lt;/code&gt; to share auth state across all components.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/lib/auth.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;,&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;react&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;api&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;./api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AuthState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&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="nl"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&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;AuthContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AuthState&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;function&lt;/span&gt; &lt;span class="nf"&gt;AuthProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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="c1"&gt;// On mount: validate stored token by calling /me&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&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;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;api&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/auth/me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&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;setUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&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;span class="k"&gt;catch&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="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_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;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&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;span class="k"&gt;finally&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;setLoading&lt;/span&gt;&lt;span class="p"&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&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="p"&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;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Map full_name → name for frontend consistency&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&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;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&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="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_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;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&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="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="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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AuthContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AuthContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthContext&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;useAuth must be used within AuthProvider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The API returns &lt;code&gt;full_name&lt;/code&gt; but we map it to &lt;code&gt;name&lt;/code&gt; in the frontend &lt;code&gt;User&lt;/code&gt; interface for simplicity. This mapping happens in three places: &lt;code&gt;login&lt;/code&gt;, &lt;code&gt;register&lt;/code&gt;, and the &lt;code&gt;/me&lt;/code&gt; response handler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Wire Into Layout — &lt;code&gt;src/app/layout.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;AuthProvider&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;@/lib/auth&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AuthProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AuthProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h2&gt;
  
  
  Frontend: Register Page
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/app/(auth)/register/page.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&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/navigation&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;Link&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/link&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&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;@/lib/auth&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RegisterPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPassword&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCompanyName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="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;setError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setLoading&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&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;// Redirect to login after registration&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Registration failed&lt;/span&gt;&lt;span class="dl"&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;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&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="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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Create Account&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;     &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Full name"&lt;/span&gt;    &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;       &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;       &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;        &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;     &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;     &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setCompanyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Company name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Creating...&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;Create Account&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Already have an account? &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign in&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h2&gt;
  
  
  Frontend: Login Page
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/app/(auth)/login/page.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&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;react&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&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/navigation&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;Link&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/link&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&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;@/lib/auth&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LoginPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPassword&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="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;setError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setLoading&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Redirect to dashboard&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login failed&lt;/span&gt;&lt;span class="dl"&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;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&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="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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome Back&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;    &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Signing in...&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;Sign in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Don't have an account? &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/register"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Create one&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h2&gt;
  
  
  Frontend: Protected Routes
&lt;/h2&gt;

&lt;p&gt;Wrap dashboard pages with a layout that redirects unauthenticated users.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/app/(dashboard)/layout.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&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/navigation&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&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;@/lib/auth&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DashboardLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&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;loading&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&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="kc"&gt;null&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;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the User in Components
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&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;@/lib/auth&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProfileCard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome, &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign Out&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h2&gt;
  
  
  Token Refresh Flow
&lt;/h2&gt;

&lt;p&gt;The API client handles token refresh &lt;strong&gt;automatically&lt;/strong&gt;. Here's the sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser makes a request (e.g. &lt;code&gt;GET /api/v2/messages&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;API Client attaches the stored &lt;code&gt;Bearer&lt;/code&gt; token&lt;/li&gt;
&lt;li&gt;If the API returns &lt;code&gt;401 Unauthorized&lt;/code&gt; (token expired):

&lt;ul&gt;
&lt;li&gt;Client sends &lt;code&gt;POST /api/v2/auth/refresh&lt;/code&gt; with the refresh token&lt;/li&gt;
&lt;li&gt;On success: stores the new tokens and &lt;strong&gt;retries the original request&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;On failure: clears all tokens and redirects to &lt;code&gt;/login&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Token Lifetimes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Lifetime&lt;/th&gt;
&lt;th&gt;Storage Key&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Access Token&lt;/td&gt;
&lt;td&gt;30 minutes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localStorage("mm_token")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh Token&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localStorage("mm_refresh")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; If both tokens are expired (user inactive for 7+ days), the client clears storage and redirects to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Common Issues &amp;amp; Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;TypeError: Cannot assign string to property ... of type DateTimeImmutable&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; &lt;code&gt;DatabaseUserProvider&lt;/code&gt; in MonkeysLegion ≤ 2.0.7 does raw assignment from PDO strings to typed properties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Upgrade to &lt;code&gt;monkeyscloud/monkeyslegion:^2.0.8&lt;/code&gt; which includes &lt;code&gt;castValue()&lt;/code&gt; type coercion in the hydrator.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;code&gt;/me&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt; user — &lt;code&gt;$request-&amp;gt;getAttribute('user')&lt;/code&gt; is null
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The &lt;code&gt;AuthenticationMiddleware&lt;/code&gt; sets the attribute as &lt;code&gt;auth.user&lt;/code&gt;, not &lt;code&gt;user&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- $user = $request-&amp;gt;getAttribute('user');
&lt;/span&gt;&lt;span class="gi"&gt;+ $user = $request-&amp;gt;getAttribute('auth.user');
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. &lt;code&gt;422 Unprocessable Content&lt;/code&gt; on register
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The email is already registered. The API returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Validation failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email already registered"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use a different email, or parse the error details in the frontend to show a specific message.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. &lt;code&gt;full_name&lt;/code&gt; vs &lt;code&gt;name&lt;/code&gt; mismatch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The API returns &lt;code&gt;full_name&lt;/code&gt; but your frontend &lt;code&gt;User&lt;/code&gt; interface expects &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Map the field in every place you read user data:&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="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. CORS errors from &lt;code&gt;localhost:3000&lt;/code&gt; → &lt;code&gt;localhost:8088&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The API needs CORS headers for cross-origin requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Add CORS middleware in your MonkeysLegion middleware stack:&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/middleware.mlc&lt;/span&gt;
&lt;span class="nc"&gt;MonkeysLegion\Http\Middleware\CorsMiddleware&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or set the environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CORS_ORIGINS=http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6. Page redirects to &lt;code&gt;/login&lt;/code&gt; on refresh despite being logged in
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The &lt;code&gt;/me&lt;/code&gt; endpoint crashes (often the &lt;code&gt;DateTimeImmutable&lt;/code&gt; bug), so the auth provider clears tokens and sets &lt;code&gt;user = null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Upgrade MonkeysLegion to &lt;code&gt;2.0.8+&lt;/code&gt; and verify &lt;code&gt;/me&lt;/code&gt; works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8088/api/v2/auth/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Register a user via curl&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8088/api/v2/auth/register &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "John Doe",
    "email": "john@example.com",
    "password": "securepass123",
    "company_name": "Acme Inc."
  }'&lt;/span&gt;

&lt;span class="c"&gt;# Login&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8088/api/v2/auth/login &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"email": "john@example.com", "password": "securepass123"}'&lt;/span&gt;

&lt;span class="c"&gt;# Get current user&lt;/span&gt;
curl http://localhost:8088/api/v2/auth/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;access_token&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Refresh token&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8088/api/v2/auth/refresh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"refresh_token": "&amp;lt;refresh_token&amp;gt;"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://monkeyslegion.com/" rel="noopener noreferrer"&gt;MonkeysLegion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>symfony</category>
      <category>react</category>
    </item>
    <item>
      <title>MonkeysLegion 2.0 Is Released — Introducing Apex AI and a Faster PHP Runtime</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Tue, 28 Apr 2026 22:43:51 +0000</pubDate>
      <link>https://dev.to/yorchperaza/monkeyslegion-20-is-released-introducing-apex-ai-and-a-faster-php-runtime-1m57</link>
      <guid>https://dev.to/yorchperaza/monkeyslegion-20-is-released-introducing-apex-ai-and-a-faster-php-runtime-1m57</guid>
      <description>&lt;p&gt;MonkeysLegion 2.0 is officially released.&lt;/p&gt;

&lt;p&gt;This is more than a framework update. It is a full architectural step forward for the MonkeysLegion ecosystem: a faster runtime, a cleaner PHP 8.4+ foundation, a stronger modular package structure, and a new AI orchestration layer built directly for modern PHP applications.&lt;/p&gt;

&lt;p&gt;PHP has always been one of the most productive languages on the web.&lt;/p&gt;

&lt;p&gt;It powers SaaS platforms, APIs, internal tools, content systems, enterprise applications, dashboards, automation workflows, and products used by millions of people every day.&lt;/p&gt;

&lt;p&gt;But modern teams are building in a different environment now.&lt;/p&gt;

&lt;p&gt;They need faster boot times.&lt;br&gt;&lt;br&gt;
They need clean architecture.&lt;br&gt;&lt;br&gt;
They need APIs by default.&lt;br&gt;&lt;br&gt;
They need cloud-native deployment.&lt;br&gt;&lt;br&gt;
They need observability.&lt;br&gt;&lt;br&gt;
They need DevOps-friendly workflows.&lt;br&gt;&lt;br&gt;
They need AI-ready applications.&lt;br&gt;&lt;br&gt;
They need developer experience without hidden runtime magic.  &lt;/p&gt;

&lt;p&gt;That is why we are releasing &lt;strong&gt;MonkeysLegion 2.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 is built for developers and teams who want the clarity of raw PHP, the structure of a serious framework, the productivity of a modular ecosystem, and the performance profile of a runtime that gets out of the way.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why MonkeysLegion 2.0 Matters
&lt;/h2&gt;

&lt;p&gt;Laravel built an incredible ecosystem.&lt;/p&gt;

&lt;p&gt;Forge, Vapor, Nova, Cashier, queues, jobs, billing tools, starter kits, and one of the largest PHP communities in the world. If you need a batteries-included SaaS starter, Laravel is hard to beat.&lt;/p&gt;

&lt;p&gt;Symfony powers some of the most mission-critical enterprise systems on earth.&lt;/p&gt;

&lt;p&gt;Its profiler, debug toolbar, reusable components, release discipline, and enterprise credibility are best-in-class.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is built for a different moment.&lt;/p&gt;

&lt;p&gt;A moment where teams want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raw throughput&lt;/li&gt;
&lt;li&gt;PHP 8.4+ architecture&lt;/li&gt;
&lt;li&gt;Attribute-first development&lt;/li&gt;
&lt;li&gt;Minimal runtime overhead&lt;/li&gt;
&lt;li&gt;Clean dependency injection&lt;/li&gt;
&lt;li&gt;Modular packages&lt;/li&gt;
&lt;li&gt;API-first structure&lt;/li&gt;
&lt;li&gt;DevOps automation&lt;/li&gt;
&lt;li&gt;Cloud-native deployment&lt;/li&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;li&gt;AI orchestration&lt;/li&gt;
&lt;li&gt;Enterprise-ready foundations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No unnecessary heavy boot process.&lt;br&gt;&lt;br&gt;
No runtime proxies.&lt;br&gt;&lt;br&gt;
No hidden magic.&lt;br&gt;&lt;br&gt;
No fighting the framework.&lt;/p&gt;

&lt;p&gt;Just your code, clean architecture, and a framework designed to move fast from local development to production.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Changed in 2.0
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is a full architectural pass across the framework and package ecosystem.&lt;/p&gt;

&lt;p&gt;The release focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.4+ native architecture&lt;/li&gt;
&lt;li&gt;Attribute-first routing and configuration&lt;/li&gt;
&lt;li&gt;Compiled dependency injection&lt;/li&gt;
&lt;li&gt;Typed MLC configuration&lt;/li&gt;
&lt;li&gt;PSR-compliant HTTP and middleware layers&lt;/li&gt;
&lt;li&gt;Production-ready security middleware&lt;/li&gt;
&lt;li&gt;OpenAPI-first API development&lt;/li&gt;
&lt;li&gt;A modular package ecosystem&lt;/li&gt;
&lt;li&gt;Better developer tooling&lt;/li&gt;
&lt;li&gt;Faster runtime behavior&lt;/li&gt;
&lt;li&gt;AI orchestration with Apex AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make PHP feel modern again — fast, clean, modular, typed, observable, deployable, and ready for AI-native applications.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  PHP 8.4+ Native Architecture
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is designed around modern PHP instead of legacy conventions.&lt;/p&gt;

&lt;p&gt;The framework is moving deeper into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strict types&lt;/li&gt;
&lt;li&gt;PHP attributes&lt;/li&gt;
&lt;li&gt;Property hooks&lt;/li&gt;
&lt;li&gt;Constructor injection&lt;/li&gt;
&lt;li&gt;Typed configuration&lt;/li&gt;
&lt;li&gt;Compiled containers&lt;/li&gt;
&lt;li&gt;PSR standards&lt;/li&gt;
&lt;li&gt;Clean service boundaries&lt;/li&gt;
&lt;li&gt;Lightweight runtime behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main idea is that your application should be readable from the code itself.&lt;/p&gt;

&lt;p&gt;Routes should be visible.&lt;br&gt;&lt;br&gt;
Services should be explicit.&lt;br&gt;&lt;br&gt;
Configuration should be understandable.&lt;br&gt;&lt;br&gt;
Controllers should be clean.&lt;br&gt;&lt;br&gt;
Database logic should be predictable.&lt;br&gt;&lt;br&gt;
Deployment should not require guessing.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 keeps the code close to the developer.&lt;/p&gt;


&lt;h2&gt;
  
  
  Attribute-First Development
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 uses PHP attributes as a first-class architecture pattern.&lt;/p&gt;

&lt;p&gt;Routes, validation rules, service providers, commands, middleware, and AI tools can be declared close to the code they affect.&lt;/p&gt;

&lt;p&gt;Example:&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;MonkeysLegion\Http\Attributes\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Psr\Http\Message\ResponseInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;#[Route('/api/projects', methods: ['GET'])]&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;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;ResponseInterface&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;json&lt;/span&gt;&lt;span class="p"&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;projects&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;list&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;This makes applications easier to understand.&lt;/p&gt;

&lt;p&gt;You open a controller and immediately see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The endpoint&lt;/li&gt;
&lt;li&gt;The HTTP method&lt;/li&gt;
&lt;li&gt;The handler&lt;/li&gt;
&lt;li&gt;The dependencies&lt;/li&gt;
&lt;li&gt;The response flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams building APIs, dashboards, SaaS platforms, internal tools, CMS platforms, or microservices, this matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compiled Dependency Injection
&lt;/h2&gt;

&lt;p&gt;Dependency injection should help developers, not confuse them.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 continues the direction of a clean, predictable container that favors explicit services, typed dependencies, and production compilation.&lt;/p&gt;

&lt;p&gt;The goal is not to create a magical container that hides everything.&lt;/p&gt;

&lt;p&gt;The goal is to make dependency management simple:&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;Psr\Log\LoggerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectService&lt;/span&gt;
&lt;span class="p"&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ProjectRepository&lt;/span&gt; &lt;span class="nv"&gt;$projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;LoggerInterface&lt;/span&gt; &lt;span class="nv"&gt;$logger&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;Your services are clear.&lt;br&gt;&lt;br&gt;
Your dependencies are explicit.&lt;br&gt;&lt;br&gt;
Your application is easier to test.&lt;br&gt;&lt;br&gt;
Your architecture is easier to reason about.&lt;/p&gt;

&lt;p&gt;In production, the framework can compile container definitions so applications avoid unnecessary runtime reflection on the hot path.&lt;/p&gt;

&lt;p&gt;That is one of the core performance principles behind MonkeysLegion 2.0.&lt;/p&gt;


&lt;h2&gt;
  
  
  Performance Is a Core Feature
&lt;/h2&gt;

&lt;p&gt;Performance is not a marketing layer in MonkeysLegion 2.0.&lt;/p&gt;

&lt;p&gt;It is part of the architecture.&lt;/p&gt;

&lt;p&gt;The framework is designed around zero-magic PHP objects, compiled routing, compiled dependency injection, attribute discovery at cache/build time, and a lean PSR middleware pipeline.&lt;/p&gt;

&lt;p&gt;The performance direction is based on several key principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero-magic architecture&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Entities, DTOs, resources, and enums should behave like plain PHP objects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No proxy-heavy runtime model&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The framework avoids forcing every application through runtime proxy layers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compiled routing and DI&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Discovery happens before production runtime where possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lean middleware pipeline&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Middleware should be powerful, but the request path should remain predictable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PHP 8.4+ features&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The framework embraces modern PHP language improvements instead of simulating them with legacy patterns.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the current MonkeysLegion benchmarks, the framework is positioned around major improvements over heavier runtime approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entity creation: around &lt;strong&gt;140× faster than Laravel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;DTO construction: around &lt;strong&gt;60× faster than Laravel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enum operations: around &lt;strong&gt;25× faster than Laravel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Resource serialization: around &lt;strong&gt;5.5× faster than Laravel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;HTTP throughput: around &lt;strong&gt;12.5K requests/sec vs Laravel’s ~2.1K requests/sec&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Cold boot memory: significantly lower than heavier full-stack boot processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These benchmark numbers should be understood in context: benchmarks depend on hardware, PHP version, configuration, JIT, opcache, test harness, and workload.&lt;/p&gt;

&lt;p&gt;But the architectural direction is clear:&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 is designed to reduce runtime overhead and make high-performance PHP applications easier to build.&lt;/p&gt;


&lt;h2&gt;
  
  
  API-First by Default
&lt;/h2&gt;

&lt;p&gt;Modern applications are API-driven.&lt;/p&gt;

&lt;p&gt;A backend is rarely just a backend anymore. It may serve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A web app&lt;/li&gt;
&lt;li&gt;A mobile app&lt;/li&gt;
&lt;li&gt;A dashboard&lt;/li&gt;
&lt;li&gt;A CMS&lt;/li&gt;
&lt;li&gt;A CLI&lt;/li&gt;
&lt;li&gt;A partner integration&lt;/li&gt;
&lt;li&gt;An AI workflow&lt;/li&gt;
&lt;li&gt;A microservice&lt;/li&gt;
&lt;li&gt;A third-party API consumer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MonkeysLegion 2.0 is designed with APIs as a first-class concern.&lt;/p&gt;

&lt;p&gt;The framework and skeleton are moving toward a complete API foundation with support for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PSR-7 and PSR-15 HTTP handling&lt;/li&gt;
&lt;li&gt;Middleware pipelines&lt;/li&gt;
&lt;li&gt;JWT authentication&lt;/li&gt;
&lt;li&gt;RBAC&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;DTO binding&lt;/li&gt;
&lt;li&gt;OpenAPI documentation&lt;/li&gt;
&lt;li&gt;Swagger UI&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Security headers&lt;/li&gt;
&lt;li&gt;Request IDs&lt;/li&gt;
&lt;li&gt;CORS&lt;/li&gt;
&lt;li&gt;Trusted proxies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means teams can build real production APIs without stitching together half a dozen disconnected tools.&lt;/p&gt;


&lt;h2&gt;
  
  
  Introducing Apex AI
&lt;/h2&gt;

&lt;p&gt;The biggest new direction in the MonkeysLegion ecosystem is &lt;strong&gt;Apex AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Apex AI is an AI orchestration package for PHP 8.4+.&lt;/p&gt;

&lt;p&gt;It is not just another OpenAI wrapper.&lt;/p&gt;

&lt;p&gt;Apex AI is designed to bring production-ready AI infrastructure into PHP with a clean, framework-native developer experience.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build AI-powered applications in PHP without stitching together multiple disconnected tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apex AI introduces a unified way to work with multiple AI providers, route requests intelligently, build pipelines, manage cost, use tools, stream responses, and create real AI workflows inside PHP applications.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Apex AI Brings to PHP
&lt;/h2&gt;

&lt;p&gt;Apex AI is designed around the needs of real production applications.&lt;/p&gt;

&lt;p&gt;It focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unified AI provider interface&lt;/li&gt;
&lt;li&gt;Multi-provider routing&lt;/li&gt;
&lt;li&gt;OpenAI support&lt;/li&gt;
&lt;li&gt;Anthropic support&lt;/li&gt;
&lt;li&gt;Google AI Studio support&lt;/li&gt;
&lt;li&gt;Vertex AI support&lt;/li&gt;
&lt;li&gt;Ollama/local model support&lt;/li&gt;
&lt;li&gt;Smart model routing&lt;/li&gt;
&lt;li&gt;Fallback chains&lt;/li&gt;
&lt;li&gt;Declarative AI pipelines&lt;/li&gt;
&lt;li&gt;Guardrails&lt;/li&gt;
&lt;li&gt;Structured outputs&lt;/li&gt;
&lt;li&gt;Tool calling with PHP attributes&lt;/li&gt;
&lt;li&gt;Streaming with Server-Sent Events&lt;/li&gt;
&lt;li&gt;Agent-style workflows&lt;/li&gt;
&lt;li&gt;Multi-agent crews&lt;/li&gt;
&lt;li&gt;Memory strategies&lt;/li&gt;
&lt;li&gt;Cost tracking&lt;/li&gt;
&lt;li&gt;Budget controls&lt;/li&gt;
&lt;li&gt;Model Context Protocol support&lt;/li&gt;
&lt;li&gt;Clean integration with MonkeysLegion applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because AI development is moving beyond simple prompt calls.&lt;/p&gt;

&lt;p&gt;Modern AI applications need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Routing&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Fallbacks&lt;/li&gt;
&lt;li&gt;Guardrails&lt;/li&gt;
&lt;li&gt;Tool execution&lt;/li&gt;
&lt;li&gt;Structured responses&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;Context management&lt;/li&gt;
&lt;li&gt;Cost controls&lt;/li&gt;
&lt;li&gt;Streaming&lt;/li&gt;
&lt;li&gt;Evaluation&lt;/li&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apex AI brings these ideas into PHP as part of the MonkeysLegion ecosystem.&lt;/p&gt;


&lt;h2&gt;
  
  
  AI-Native PHP Applications
&lt;/h2&gt;

&lt;p&gt;PHP developers should not need to leave PHP to build serious AI workflows.&lt;/p&gt;

&lt;p&gt;Many companies already have PHP backends, PHP teams, PHP infrastructure, PHP CMS platforms, PHP APIs, and PHP business logic.&lt;/p&gt;

&lt;p&gt;The question is not whether PHP can be used for AI applications.&lt;/p&gt;

&lt;p&gt;The question is whether the ecosystem gives PHP developers the right tools.&lt;/p&gt;

&lt;p&gt;Apex AI is built around that belief.&lt;/p&gt;

&lt;p&gt;With Apex AI, a PHP application can become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AI-powered CMS&lt;/li&gt;
&lt;li&gt;An AI-assisted dashboard&lt;/li&gt;
&lt;li&gt;A document processing system&lt;/li&gt;
&lt;li&gt;A content generation platform&lt;/li&gt;
&lt;li&gt;A support automation tool&lt;/li&gt;
&lt;li&gt;An internal agent workflow&lt;/li&gt;
&lt;li&gt;A SaaS product with AI features&lt;/li&gt;
&lt;li&gt;A developer platform with AI orchestration&lt;/li&gt;
&lt;li&gt;A backend that routes between cloud and local models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where MonkeysLegion 2.0 becomes more than a framework release.&lt;/p&gt;

&lt;p&gt;It becomes the foundation for AI-native PHP development.&lt;/p&gt;


&lt;h2&gt;
  
  
  Example: Tool Calling with Attributes
&lt;/h2&gt;

&lt;p&gt;Apex AI is designed to feel natural inside modern PHP.&lt;/p&gt;

&lt;p&gt;Instead of forcing AI workflows into disconnected configuration files, tools can be represented as PHP code.&lt;/p&gt;

&lt;p&gt;Example:&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;MonkeysLegion\Apex\Attributes\Tool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'find_project'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Find a project by ID and return its current status.'&lt;/span&gt;
  &lt;span class="p"&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;findProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&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="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&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;This keeps AI tools close to the domain logic of the application.&lt;/p&gt;

&lt;p&gt;The result is easier to read, easier to test, and easier to maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Built for SaaS, Internal Tools, and Enterprise Platforms
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is not only for small apps.&lt;/p&gt;

&lt;p&gt;It is being designed for companies building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS platforms&lt;/li&gt;
&lt;li&gt;Internal business tools&lt;/li&gt;
&lt;li&gt;Developer portals&lt;/li&gt;
&lt;li&gt;Admin dashboards&lt;/li&gt;
&lt;li&gt;API platforms&lt;/li&gt;
&lt;li&gt;Automation systems&lt;/li&gt;
&lt;li&gt;AI-enabled products&lt;/li&gt;
&lt;li&gt;Cloud-native services&lt;/li&gt;
&lt;li&gt;CMS-driven websites&lt;/li&gt;
&lt;li&gt;Enterprise applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many companies do not need another heavy framework that assumes one way of working.&lt;/p&gt;

&lt;p&gt;They need a framework that lets them move fast while keeping the codebase clean.&lt;/p&gt;

&lt;p&gt;MonkeysLegion gives teams a structured foundation without forcing unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Apex AI extends that foundation into AI-powered product development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Modular Ecosystem
&lt;/h2&gt;

&lt;p&gt;One of the most important ideas behind MonkeysLegion is modularity.&lt;/p&gt;

&lt;p&gt;You should not need to install a giant framework if you only need one part of it.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is moving toward a package ecosystem where teams can use the pieces they need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core&lt;/li&gt;
&lt;li&gt;Router&lt;/li&gt;
&lt;li&gt;Dependency Injection&lt;/li&gt;
&lt;li&gt;HTTP&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Query Builder&lt;/li&gt;
&lt;li&gt;Migration&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Internationalization&lt;/li&gt;
&lt;li&gt;Telemetry&lt;/li&gt;
&lt;li&gt;DevTools&lt;/li&gt;
&lt;li&gt;CLI&lt;/li&gt;
&lt;li&gt;GraphQL&lt;/li&gt;
&lt;li&gt;Apex AI&lt;/li&gt;
&lt;li&gt;Cloud tooling&lt;/li&gt;
&lt;li&gt;Testing utilities&lt;/li&gt;
&lt;li&gt;CMS-related packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This package-first approach makes the framework flexible.&lt;/p&gt;

&lt;p&gt;Small projects can stay small.&lt;br&gt;&lt;br&gt;
Large projects can grow with structure.&lt;br&gt;&lt;br&gt;
Enterprise teams can standardize around the packages that matter to them.&lt;/p&gt;


&lt;h2&gt;
  
  
  Telemetry and Observability
&lt;/h2&gt;

&lt;p&gt;Modern applications need visibility.&lt;/p&gt;

&lt;p&gt;It is not enough for a framework to run code. Teams need to understand what is happening in production.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 treats observability as part of the developer platform, not as an afterthought.&lt;/p&gt;

&lt;p&gt;That includes visibility into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request performance&lt;/li&gt;
&lt;li&gt;Application health&lt;/li&gt;
&lt;li&gt;Security events&lt;/li&gt;
&lt;li&gt;API usage&lt;/li&gt;
&lt;li&gt;Background processes&lt;/li&gt;
&lt;li&gt;AI requests&lt;/li&gt;
&lt;li&gt;Model routing&lt;/li&gt;
&lt;li&gt;Provider fallbacks&lt;/li&gt;
&lt;li&gt;Cost tracking&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Error rates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes even more important with Apex AI.&lt;/p&gt;

&lt;p&gt;AI applications need observability because model behavior, token usage, latency, provider availability, and costs all affect production reliability.&lt;/p&gt;


&lt;h2&gt;
  
  
  Cloud-Native From the Start
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is not being designed only for local development.&lt;/p&gt;

&lt;p&gt;It is being built with deployment in mind.&lt;/p&gt;

&lt;p&gt;Modern teams need applications that can move from local development to containers, servers, CI/CD pipelines, and cloud environments without massive rewrites.&lt;/p&gt;

&lt;p&gt;That matters because software delivery is no longer just about writing PHP classes.&lt;/p&gt;

&lt;p&gt;It is about the full lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;Package&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;li&gt;Monitor&lt;/li&gt;
&lt;li&gt;Scale&lt;/li&gt;
&lt;li&gt;Improve&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MonkeysLegion 2.0 is being shaped around that complete workflow.&lt;/p&gt;


&lt;h2&gt;
  
  
  A Better Developer Experience Without Heavy Runtime Cost
&lt;/h2&gt;

&lt;p&gt;Developer experience matters.&lt;/p&gt;

&lt;p&gt;But developer experience should not require hiding everything behind magic.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 is focused on a balanced philosophy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give developers speed, structure, and useful defaults — without making the runtime heavy or the code difficult to understand.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear file structure&lt;/li&gt;
&lt;li&gt;Fast boot process&lt;/li&gt;
&lt;li&gt;Explicit dependencies&lt;/li&gt;
&lt;li&gt;Attribute-based configuration&lt;/li&gt;
&lt;li&gt;Useful CLI commands&lt;/li&gt;
&lt;li&gt;Strong defaults&lt;/li&gt;
&lt;li&gt;Modular packages&lt;/li&gt;
&lt;li&gt;Production-ready skeleton&lt;/li&gt;
&lt;li&gt;Easy local setup&lt;/li&gt;
&lt;li&gt;Cloud-ready conventions&lt;/li&gt;
&lt;li&gt;AI-ready workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The framework should feel productive without becoming opaque.&lt;/p&gt;


&lt;h2&gt;
  
  
  Inspired by the Best, Built With a Different Philosophy
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is not trying to replace Laravel or Symfony.&lt;/p&gt;

&lt;p&gt;They are excellent frameworks with massive ecosystems and proven track records.&lt;/p&gt;

&lt;p&gt;But not every team wants the same tradeoffs.&lt;/p&gt;

&lt;p&gt;Laravel is great when you want a large ecosystem and rapid application scaffolding.&lt;/p&gt;

&lt;p&gt;Symfony is great when you want enterprise components, stability, and deep configurability.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is for teams that want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A lighter framework&lt;/li&gt;
&lt;li&gt;Less magic&lt;/li&gt;
&lt;li&gt;More direct control&lt;/li&gt;
&lt;li&gt;Modern PHP-first architecture&lt;/li&gt;
&lt;li&gt;Fast APIs&lt;/li&gt;
&lt;li&gt;Modular packages&lt;/li&gt;
&lt;li&gt;Cloud-native defaults&lt;/li&gt;
&lt;li&gt;Strong developer tooling&lt;/li&gt;
&lt;li&gt;Clean enterprise structure&lt;/li&gt;
&lt;li&gt;AI orchestration built into the ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not about copying the old model.&lt;/p&gt;

&lt;p&gt;It is about building a framework for where PHP is going next.&lt;/p&gt;


&lt;h2&gt;
  
  
  Built as an Ecosystem, Not Just a Framework
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is part of a bigger vision.&lt;/p&gt;

&lt;p&gt;The framework is the foundation, but the ecosystem is the real goal.&lt;/p&gt;

&lt;p&gt;That ecosystem includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MonkeysLegion Framework&lt;/li&gt;
&lt;li&gt;MonkeysLegion Skeleton&lt;/li&gt;
&lt;li&gt;MonkeysLegion DevTools&lt;/li&gt;
&lt;li&gt;MonkeysLegion GraphQL&lt;/li&gt;
&lt;li&gt;MonkeysLegion Telemetry&lt;/li&gt;
&lt;li&gt;MonkeysLegion Apex AI&lt;/li&gt;
&lt;li&gt;MonkeysCMS&lt;/li&gt;
&lt;li&gt;Cloud deployment tooling&lt;/li&gt;
&lt;li&gt;AI-ready developer workflows&lt;/li&gt;
&lt;li&gt;Enterprise package roadmap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because modern teams do not only need a router or container.&lt;/p&gt;

&lt;p&gt;They need a complete development platform.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is moving toward that full-stack ecosystem while keeping the core lightweight and modular.&lt;/p&gt;


&lt;h2&gt;
  
  
  MonkeysCMS and the Future of AI-Ready Content Platforms
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion also powers the direction behind MonkeysCMS.&lt;/p&gt;

&lt;p&gt;MonkeysCMS is being designed as a modern, code-first CMS built on top of the MonkeysLegion framework.&lt;/p&gt;

&lt;p&gt;This is one of the strongest examples of why MonkeysLegion matters.&lt;/p&gt;

&lt;p&gt;A framework is not only valuable when it can serve a JSON response.&lt;/p&gt;

&lt;p&gt;It becomes powerful when it can support real products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CMS platforms&lt;/li&gt;
&lt;li&gt;Admin systems&lt;/li&gt;
&lt;li&gt;API products&lt;/li&gt;
&lt;li&gt;AI workflows&lt;/li&gt;
&lt;li&gt;Enterprise dashboards&lt;/li&gt;
&lt;li&gt;Developer platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Apex AI, the CMS direction becomes even stronger.&lt;/p&gt;

&lt;p&gt;Content systems are one of the clearest places where AI can help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate drafts&lt;/li&gt;
&lt;li&gt;Improve content&lt;/li&gt;
&lt;li&gt;Critique content&lt;/li&gt;
&lt;li&gt;Translate content&lt;/li&gt;
&lt;li&gt;Classify content&lt;/li&gt;
&lt;li&gt;Extract metadata&lt;/li&gt;
&lt;li&gt;Build summaries&lt;/li&gt;
&lt;li&gt;Power editorial assistants&lt;/li&gt;
&lt;li&gt;Automate workflow tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MonkeysLegion 2.0 is the foundation for that future.&lt;/p&gt;


&lt;h2&gt;
  
  
  Open Source and Community-Driven
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is open source.&lt;/p&gt;

&lt;p&gt;The project started from a simple belief:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modern PHP projects deserve a framework that is fast, cloud-native, AI-ready, and clear to work with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Version 2.0 is a call to developers, agencies, founders, and engineering teams:&lt;/p&gt;

&lt;p&gt;Come test it.&lt;br&gt;&lt;br&gt;
Break it.&lt;br&gt;&lt;br&gt;
Improve it.&lt;br&gt;&lt;br&gt;
Build with it.&lt;br&gt;&lt;br&gt;
Open issues.&lt;br&gt;&lt;br&gt;
Submit pull requests.&lt;br&gt;&lt;br&gt;
Share ideas.&lt;br&gt;&lt;br&gt;
Help shape the next generation of PHP tooling.&lt;/p&gt;


&lt;h2&gt;
  
  
  Who MonkeysLegion 2.0 Is For
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is for developers and teams who want to build serious software with PHP.&lt;/p&gt;

&lt;p&gt;It is for the solo founder building a SaaS product.&lt;/p&gt;

&lt;p&gt;It is for the agency building multiple client platforms.&lt;/p&gt;

&lt;p&gt;It is for the backend engineer building APIs.&lt;/p&gt;

&lt;p&gt;It is for the enterprise team that needs maintainable internal tools.&lt;/p&gt;

&lt;p&gt;It is for the startup that wants speed without technical debt.&lt;/p&gt;

&lt;p&gt;It is for the developer who loves PHP but wants a framework that feels modern, clean, fast, and ready for AI-powered products.&lt;/p&gt;

&lt;p&gt;It is for builders who want control.&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The easiest way to start is with the official skeleton.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project monkeyscloud/monkeyslegion-skeleton my-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
composer serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can begin building APIs, services, dashboards, internal tools, SaaS applications, AI workflows, CMS platforms, or custom products using the MonkeysLegion architecture.&lt;/p&gt;

&lt;p&gt;Useful links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://monkeyslegion.com" rel="noopener noreferrer"&gt;monkeyslegion.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Release article: &lt;a href="https://monkeyslegion.com/news/2026-04/monkeyslegion-2-announcing" rel="noopener noreferrer"&gt;monkeyslegion.com/news/2026-04/monkeyslegion-2-announcing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Framework: &lt;a href="https://github.com/MonkeysCloud/MonkeysLegion" rel="noopener noreferrer"&gt;github.com/MonkeysCloud/MonkeysLegion&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Skeleton: &lt;a href="https://github.com/MonkeysCloud/MonkeysLegion-Skeleton" rel="noopener noreferrer"&gt;github.com/MonkeysCloud/MonkeysLegion-Skeleton&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is built with a clear belief:&lt;/p&gt;

&lt;p&gt;PHP is not done.&lt;/p&gt;

&lt;p&gt;PHP is still one of the most important languages on the web. But the ecosystem needs new ideas, new tooling, and new frameworks that match how software is built today.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is our answer.&lt;/p&gt;

&lt;p&gt;A feather-light framework.&lt;br&gt;&lt;br&gt;
A modular ecosystem.&lt;br&gt;&lt;br&gt;
A cloud-native foundation.&lt;br&gt;&lt;br&gt;
A clean architecture for modern PHP.&lt;br&gt;&lt;br&gt;
A faster runtime path.&lt;br&gt;&lt;br&gt;
A developer platform built for speed, clarity, and scale.&lt;br&gt;&lt;br&gt;
An AI orchestration layer for the next generation of PHP applications.&lt;/p&gt;

&lt;p&gt;Version 2.0 is not the finish line.&lt;/p&gt;

&lt;p&gt;It is the foundation.&lt;/p&gt;

&lt;p&gt;Welcome to MonkeysLegion 2.0.&lt;/p&gt;

&lt;p&gt;Welcome to Apex AI.&lt;/p&gt;

&lt;p&gt;Let’s build the next era of PHP.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>symfony</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Replaced GitHub + Jira + Vercel + PlanetScale + Datadog With One Free Platform</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Mon, 06 Apr 2026 15:07:29 +0000</pubDate>
      <link>https://dev.to/yorchperaza/i-replaced-github-jira-vercel-planetscale-datadog-with-one-free-platform-5gkl</link>
      <guid>https://dev.to/yorchperaza/i-replaced-github-jira-vercel-planetscale-datadog-with-one-free-platform-5gkl</guid>
      <description>&lt;p&gt;Every month I was paying for five tools that didn't talk to each other.&lt;/p&gt;

&lt;p&gt;GitHub for code ($20/mo for the team). Jira for tasks ($8/user/mo). Vercel for some deploys ($20/user/mo). A database host ($40/mo). Datadog for monitoring ($75/mo). Plus the Slack tax of context-switching between all of them 50 times a day.&lt;/p&gt;

&lt;p&gt;Total: &lt;strong&gt;~$400/month.&lt;/strong&gt; Five logins. Five billing dashboards. Five permission systems. Zero integration between them.&lt;/p&gt;

&lt;p&gt;When someone on my team merged a PR, I had to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check GitHub to see the diff&lt;/li&gt;
&lt;li&gt;Switch to Jira to update the ticket&lt;/li&gt;
&lt;li&gt;Switch to Vercel to see if the deploy succeeded&lt;/li&gt;
&lt;li&gt;Switch to Datadog to check if metrics look healthy&lt;/li&gt;
&lt;li&gt;Go back to Jira to move the ticket to Done&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's five context switches for one merge. Multiply by 10 merges a day. That's 50 tab switches. Research says each context switch costs 20-30 minutes of deep focus. We were bleeding productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I Built One Platform
&lt;/h2&gt;

&lt;p&gt;I'm a developer. The answer was obvious: build one tool that does all of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://monkeys.cloud" rel="noopener noreferrer"&gt;MonkeysCloud&lt;/a&gt; is what I built. It's a single platform with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Git hosting&lt;/strong&gt; — repos, PRs, branches, merge queues, branch protection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task management&lt;/strong&gt; — Kanban boards, Scrum sprints, time tracking, labels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting&lt;/strong&gt; — 32 stacks, auto-detect, auto-build, auto-deploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Databases&lt;/strong&gt; — MySQL, PostgreSQL, Redis, MongoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt; — code review on every PR, build failure analysis, deploy risk scoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; — wiki, knowledge base, API docs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt; — metrics, logs, uptime checks, alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here's the part that matters: &lt;strong&gt;it's free.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Free" Actually Means
&lt;/h2&gt;

&lt;p&gt;Not a 14-day trial. Not "free until you hit 100 builds." Actually free:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;th&gt;Free tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compute instances per project&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 free&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database instances per project&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 free&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team members&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unlimited&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Projects&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unlimited&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git repositories&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unlimited&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom domains + SSL&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI code review&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kanban boards + sprints&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring + logs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation wiki&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Part That Changed My Workflow
&lt;/h2&gt;

&lt;p&gt;The killer feature isn't any single thing — it's the integration.&lt;/p&gt;

&lt;p&gt;When a developer creates a branch named &lt;code&gt;feature/PROJ-42-auth&lt;/code&gt;, the platform automatically links that branch, every commit on it, the PR, the build, the preview environment, and the deploy &lt;strong&gt;back to task PROJ-42&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The task timeline shows:&lt;/p&gt;

&lt;p&gt;Task created by Sarah&lt;br&gt;
Branch feature/PROJ-42-auth created&lt;br&gt;
4 commits pushed&lt;br&gt;
PR #17 opened&lt;br&gt;
AI review: 2 suggestions (resolved)&lt;br&gt;
Build #84 passed&lt;br&gt;
PR merged to main&lt;br&gt;
Deploy #91 to production ✓&lt;br&gt;
Task auto-moved to Done&lt;/p&gt;

&lt;p&gt;Nobody manually updated the task. Nobody copy-pasted a Jira ticket number. Nobody checked three tabs to verify the deploy. It just happened.&lt;/p&gt;

&lt;p&gt;My project manager sees the full lifecycle of every feature without asking anyone for a status update. The board moves itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Actually Helps
&lt;/h2&gt;

&lt;p&gt;I've been burned by "AI-powered" tools that are just ChatGPT wrappers. MonkeysAI is different because it has context — it accesses the actual codebase, task board, build logs, deploy history, and monitoring data.&lt;/p&gt;

&lt;p&gt;When a build fails, it doesn't say "check your configuration." It says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build failed: TypeScript type mismatch on line 42 of Auth.tsx. The function handleLogin expects &lt;code&gt;string&lt;/code&gt; but receives &lt;code&gt;string | undefined&lt;/code&gt;. Introduced in commit abc123. Fix: add a null check or update the type signature.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When I'm about to deploy to production, it gives a risk score:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deploy risk: MEDIUM. Changes 3 files in the payment module. Test coverage for changed code: 62% (below project average of 78%). Recommendation: verify checkout flow in preview before promoting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are not generic suggestions. They reference my actual files, my actual test coverage, my actual deploy history.&lt;/p&gt;

&lt;h2&gt;
  
  
  32 Stacks — Zero Config
&lt;/h2&gt;

&lt;p&gt;I push code. MonkeysCloud reads &lt;code&gt;composer.json&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;go.mod&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;code&gt;Gemfile&lt;/code&gt;, &lt;code&gt;Cargo.toml&lt;/code&gt;, or whatever — and generates the entire build config automatically.&lt;/p&gt;

&lt;p&gt;Stacks I've deployed without writing a single line of configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PHP:&lt;/strong&gt; Laravel, Symfony, WordPress, Drupal, my own MonkeysLegion framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript:&lt;/strong&gt; Next.js, Nuxt, Remix, SvelteKit, Astro, Express, NestJS, React, Vue, Angular&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python:&lt;/strong&gt; Django, FastAPI, Flask, Streamlit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruby:&lt;/strong&gt; Rails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go:&lt;/strong&gt; any Go project (compiles to binary, 30-second deploys)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust:&lt;/strong&gt; Actix, Axum, Rocket&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java:&lt;/strong&gt; Spring Boot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET:&lt;/strong&gt; ASP.NET Core&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elixir:&lt;/strong&gt; Phoenix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom:&lt;/strong&gt; Dockerfile, Docker Compose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can override everything, but for 90% of projects, you push code and it just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Costs (For Real)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solo developer, side project:&lt;/strong&gt; $0/month. Two free servers + two free databases. Custom domain. AI code review. Task board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Going to production:&lt;/strong&gt; $19/month. One always-on Starter instance. Keep your free instances for dev and staging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup, 5 devs, 2 projects:&lt;/strong&gt; ~$177/month. Team plan ($59) + Pro production ($79) + Plus database ($39). Everything else free. Compare to $400+ across five separate tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agency, 12 clients:&lt;/strong&gt; ~$135/month. Most clients on free instances. A few on Starter ($19 each). Team plan ($59). All client stakeholders invited free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Push your code and get a live URL in 60 seconds: &lt;a href="https://monkeys.cloud" rel="noopener noreferrer"&gt;monkeys.cloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built this because I was tired of context-switching between five tools that cost $400/month combined. Now my entire workflow — code, tasks, deploys, databases, AI, docs, monitoring — lives in one place.&lt;/p&gt;

&lt;p&gt;If you've felt the same frustration, give it 5 minutes. The worst that happens is you deploy a project for free.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Jorge, the founder of MonkeysCloud. I'm building this in public and shipping fast. Follow along or roast my work — either way, I'd love to hear what you think in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We're Giving Away Free Servers With Every Project — App, Database, Cache, Worker. No Trial. No Credit Card.</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Sun, 05 Apr 2026 21:16:28 +0000</pubDate>
      <link>https://dev.to/yorchperaza/were-giving-away-free-servers-with-every-project-app-database-cache-worker-no-trial-no-1ocf</link>
      <guid>https://dev.to/yorchperaza/were-giving-away-free-servers-with-every-project-app-database-cache-worker-no-trial-no-1ocf</guid>
      <description>&lt;p&gt;Today we're launching something that no other developer platform offers: &lt;strong&gt;every project you create on MonkeysCloud gets free compute instances and free database instances.&lt;/strong&gt; Not a 30-day trial. Not $5 in credits. Real servers that run your code and your database — for as long as you need them.&lt;/p&gt;

&lt;p&gt;Here's what that means in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get — Per Project, No Strings
&lt;/h2&gt;

&lt;p&gt;Every project on MonkeysCloud includes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instance&lt;/th&gt;
&lt;th&gt;What it runs&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free server 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your application (any of 32 stacks)&lt;/td&gt;
&lt;td&gt;1 GB RAM&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free server 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A worker, second service, or another app&lt;/td&gt;
&lt;td&gt;1 GB RAM&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free database 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MySQL 8.4, PostgreSQL 16, or MongoDB 7&lt;/td&gt;
&lt;td&gt;1 GB RAM&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free database 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Redis 7 cache, or a second database&lt;/td&gt;
&lt;td&gt;1 GB RAM&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's &lt;strong&gt;4 free instances per project.&lt;/strong&gt; Each one can run any role — app server, database, cache, queue worker, scheduler, or a custom Docker container. You choose.&lt;/p&gt;

&lt;p&gt;No credit card required. No time limit. No usage cap. You sign up, create a project, select your stack, push code, and you have a running application with a database and a cache at a live URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Exactly Runs on These Free Instances?
&lt;/h2&gt;

&lt;p&gt;Let me make this concrete. Here's what you can deploy right now for $0:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A complete Laravel stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instance 1: Laravel app (FrankenPHP, PHP 8.4)&lt;/li&gt;
&lt;li&gt;Instance 2: Queue worker (processing jobs)&lt;/li&gt;
&lt;li&gt;Database 1: MySQL 8.4 (your primary database)&lt;/li&gt;
&lt;li&gt;Database 2: Redis 7 (sessions + cache)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total cost: &lt;strong&gt;$0.&lt;/strong&gt; That's a production-capable Laravel application with a relational database, a cache layer, and background job processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Next.js full-stack app:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instance 1: Next.js (Node 22, SSR)&lt;/li&gt;
&lt;li&gt;Database 1: PostgreSQL 16&lt;/li&gt;
&lt;li&gt;Database 2: Redis 7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total cost: &lt;strong&gt;$0.&lt;/strong&gt; Three instances. Full-stack JavaScript with a real database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Django + Celery setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instance 1: Django (Gunicorn)&lt;/li&gt;
&lt;li&gt;Instance 2: Celery worker&lt;/li&gt;
&lt;li&gt;Database 1: PostgreSQL 16&lt;/li&gt;
&lt;li&gt;Database 2: Redis 7 (Celery broker)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total cost: &lt;strong&gt;$0.&lt;/strong&gt; Background tasks, a real database, and a message broker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A microservices project:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instance 1: Go API&lt;/li&gt;
&lt;li&gt;Instance 2: Node.js service&lt;/li&gt;
&lt;li&gt;Database 1: PostgreSQL 16&lt;/li&gt;
&lt;li&gt;Database 2: MongoDB 7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total cost: &lt;strong&gt;$0.&lt;/strong&gt; Two services and two different database engines.&lt;/p&gt;

&lt;p&gt;Every project. Any stack. App + database + cache + worker. Free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fine Print (There's Not Much)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sleep after 30 minutes.&lt;/strong&gt; Free instances sleep after 30 minutes with no incoming traffic. When the next request arrives, the instance wakes in 3 to 5 seconds. Your data on disk — database files, uploaded files, application state — is preserved while sleeping. This is fine for development, staging, demos, internal tools, client previews, and sites with intermittent traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom domains and SSL work.&lt;/strong&gt; Every instance — including free ones — supports custom domains with auto-provisioned SSL via Let's Encrypt. You can point &lt;code&gt;myapp.com&lt;/code&gt; at a free instance and it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two sizes of free.&lt;/strong&gt; There are actually two free sizes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;CPU&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;SSD&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shared 0.25 vCPU&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Starter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shared 0.5 vCPU&lt;/td&gt;
&lt;td&gt;2 GB&lt;/td&gt;
&lt;td&gt;10 GB&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both are free. Both sleep after 30 minutes. Both count toward your free instance quota. Pick whichever fits your workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 + 2 is the starting point.&lt;/strong&gt; A free account gets 2 compute instances + 2 database instances per project. But that quota grows as you add paid instances (more on that below).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Platform Is Free Too
&lt;/h2&gt;

&lt;p&gt;The free instances are just the infrastructure. The platform that wraps them is also free:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git hosting.&lt;/strong&gt; Unlimited private repositories with Smart HTTP and SSH access. Or mirror from GitHub, GitLab, or Bitbucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pull requests.&lt;/strong&gt; Create PRs, leave inline reviews, run merge queues, enforce branch protection. Three merge strategies: merge commit, squash, rebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI code review.&lt;/strong&gt; Every PR gets an automated review in seconds — security vulnerabilities, performance issues, bugs, and best practices. Inline comments with suggested fixes. Before your team even opens the PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task management.&lt;/strong&gt; Kanban boards and Scrum sprints. Tasks auto-link to branches, PRs, builds, and deploys through branch naming. The board updates itself — nobody manually moves tickets to "Done."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring.&lt;/strong&gt; Response times, error rates, CPU, memory, bandwidth — real-time dashboards with zero setup. Structured log viewer with search and live streaming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation.&lt;/strong&gt; Built-in Markdown wiki with version history, search, and collaborative editing. API docs, runbooks, onboarding guides — next to your code and tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unlimited team members.&lt;/strong&gt; Invite your entire team — developers, designers, product managers, clients, contractors. Everyone gets full access. No per-seat pricing. No "viewer" seats. Free.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Scales: The Tier System
&lt;/h2&gt;

&lt;p&gt;Here's the part I'm most proud of. When you add a paid instance (Plus at $39/mo or above) to any project, your &lt;strong&gt;entire organization&lt;/strong&gt; gets upgraded:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Paid Instances in Org&lt;/th&gt;
&lt;th&gt;Org Tier&lt;/th&gt;
&lt;th&gt;Max Projects&lt;/th&gt;
&lt;th&gt;Free Instances per Project&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2 + 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3 + 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2–3&lt;/td&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4 + 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4–7&lt;/td&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;5 + 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8–15&lt;/td&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;6 + 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16–30&lt;/td&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;7 + 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31+&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;7 + 7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;One paid instance upgrades every project.&lt;/strong&gt; Add a single Plus ($39/mo) to Project A, and Projects B, C, D, and E all get 3 free compute + 3 free database instances each. The paid instance benefits your entire org, not just the project it lives in.&lt;/p&gt;

&lt;p&gt;And here's the kicker: &lt;strong&gt;Plus, Pro, and Business include a 90-day free trial.&lt;/strong&gt; You add a Plus instance, pay $0 for 90 days, and immediately get Starter tier benefits across your entire organization. Five projects. Three free instances each. Before paying a single dollar.&lt;/p&gt;

&lt;h2&gt;
  
  
  32 Stacks — You Pick, We Build
&lt;/h2&gt;

&lt;p&gt;When you create a project, you select your stack from 32 options. MonkeysCloud generates the complete build configuration — base image, install commands, build steps, start command — based on your selection. Push code. We handle the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP:&lt;/strong&gt; Laravel, Symfony, WordPress, Drupal, MonkeysLegion, PHP Generic&lt;br&gt;
&lt;strong&gt;JavaScript/TypeScript:&lt;/strong&gt; Next.js, Nuxt, Remix, SvelteKit, Astro, Express, NestJS, React SPA, Vue SPA, Angular&lt;br&gt;
&lt;strong&gt;Python:&lt;/strong&gt; Django, FastAPI, Flask, Streamlit, Python Generic&lt;br&gt;
&lt;strong&gt;Ruby:&lt;/strong&gt; Rails, Ruby Generic&lt;br&gt;
&lt;strong&gt;Go&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Rust&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Java:&lt;/strong&gt; Spring Boot, Java Generic&lt;br&gt;
&lt;strong&gt;.NET&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Elixir:&lt;/strong&gt; Phoenix&lt;br&gt;
&lt;strong&gt;Other:&lt;/strong&gt; Static Site, Docker, Docker Compose&lt;/p&gt;

&lt;p&gt;You pick the stack. We configure the build pipeline. You push code. It deploys.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Costs When You Need More
&lt;/h2&gt;

&lt;p&gt;The free tier handles development, staging, demos, and low-traffic production. When you need always-on instances with more compute for real production traffic:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;SSD&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Always-On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;No (sleeps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;2 GB&lt;/td&gt;
&lt;td&gt;10 GB&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;No (sleeps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plus&lt;/td&gt;
&lt;td&gt;4 GB&lt;/td&gt;
&lt;td&gt;25 GB&lt;/td&gt;
&lt;td&gt;$39/mo &lt;em&gt;(90 days free)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;td&gt;60 GB&lt;/td&gt;
&lt;td&gt;$79/mo &lt;em&gt;(90 days free)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;td&gt;120 GB&lt;/td&gt;
&lt;td&gt;$149/mo &lt;em&gt;(90 days free)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;32 GB&lt;/td&gt;
&lt;td&gt;250 GB&lt;/td&gt;
&lt;td&gt;$299/mo&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Power&lt;/td&gt;
&lt;td&gt;64 GB&lt;/td&gt;
&lt;td&gt;500 GB&lt;/td&gt;
&lt;td&gt;$599/mo&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HC-32 → HC-64&lt;/td&gt;
&lt;td&gt;128–256 GB&lt;/td&gt;
&lt;td&gt;1–2 TB&lt;/td&gt;
&lt;td&gt;$1,199–$2,399/mo&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The jump from free to paid is &lt;strong&gt;Plus at $39/month&lt;/strong&gt; — with 90 days free. That gives you 4 GB RAM, 25 GB SSD, always-on, and immediately upgrades your org tier so every project gets more free instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who This Is For
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solo developers.&lt;/strong&gt; Deploy your side project, portfolio, or MVP with a database. Free. Custom domain. SSL. AI code review. Push to deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startups.&lt;/strong&gt; Run your development and staging environments on free instances. Add a Pro ($79, 90 days free) for production. Your 5-person team uses the platform for free — Git, tasks, deploys, monitoring, docs. Compare to $400/month across GitHub + Jira + Vercel + database host + Datadog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agencies.&lt;/strong&gt; Create a project per client. Most client sites run on free instances with a custom domain. Larger clients upgrade to Plus or Pro for always-on. Invite client stakeholders to their project board for free. Fifteen developers, twelve clients — all free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open-source projects.&lt;/strong&gt; Host your project, track issues, deploy documentation sites, and give contributors AI-reviewed PRs. Free. Apply for our Open Source Partner program for additional instance credits and a free Team plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Students and educators.&lt;/strong&gt; Learn real-world dev workflows — Git, pull requests, CI/CD, databases, monitoring — on a platform that doesn't expire after 30 days. Apply for our Education Partner program for free Team plans and instance credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Right Now
&lt;/h2&gt;

&lt;p&gt;No pitch. No "schedule a demo." No "talk to sales."&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://monkeys.cloud" rel="noopener noreferrer"&gt;monkeys.cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sign up (GitHub or Google, 10 seconds)&lt;/li&gt;
&lt;li&gt;Create a project, select your stack&lt;/li&gt;
&lt;li&gt;Push your code&lt;/li&gt;
&lt;li&gt;You have a live URL with a database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The worst that happens is you deploy a project for free and decide it's not for you.&lt;/p&gt;

&lt;p&gt;The best that happens is you replace five tools with one and never context-switch again.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://monkeys.cloud" rel="noopener noreferrer"&gt;monkeys.cloud&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ for the Skeptics
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"How do you make money if the servers are free?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The free instances are small (1–2 GB) and sleep when idle. They cost us very little to run on shared infrastructure. When your project needs always-on production hosting with dedicated compute and more resources, you add paid instances starting at $39/month with 65–79% gross margins. The free tier is the acquisition funnel. The paid tier is the business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What's the catch?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free instances sleep after 30 minutes. That means a 3–5 second wake time on the first request after sleeping. For production sites where every millisecond matters, you upgrade to a paid always-on instance. For everything else — dev, staging, demos, internal tools, side projects, client previews — the sleep/wake cycle is fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Will the free tier disappear?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We reserve the right to modify free tier limits with 30 days' notice (see our Terms). We have no plans to remove the free tier — it's our primary acquisition channel and the core of our growth model. But we're honest: we don't use the phrase "free forever" because we can't predict the future. What we can say is that the free tier is fundamental to how MonkeysCloud works, and removing it would require fundamentally changing the business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"How is this different from Railway/Render/Heroku?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Railway gives you a 30-day trial with $5 credit — then you pay per minute. Render's free tier sleeps after 15 minutes with 750 hours/month. Heroku eliminated free dynos entirely. None of them include Git hosting, task management, AI code review, documentation, or monitoring. MonkeysCloud gives you all of that — plus 4 free instances per project — in one integrated platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"How is this different from Vercel/Netlify?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vercel and Netlify are frontend-first. They deploy React, Next.js, and static sites. They don't host PHP, Python, Go, Ruby, Java, or Rust backends. They don't run MySQL, PostgreSQL, or Redis. They don't have task management or AI code review. MonkeysCloud is full-stack — 32 frameworks, real databases, real backend hosting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Can I actually run a production site on the free tier?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your site gets intermittent traffic and a 3–5 second wake time is acceptable, yes. Many blogs, portfolios, documentation sites, internal tools, and low-traffic client sites run fine on free instances with a custom domain and SSL. For high-traffic production with instant response, upgrade to Plus ($39/mo, 90 days free).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Jorge, the founder of MonkeysCloud. I built this because I was tired of paying $400/month for five tools that didn't talk to each other. Now I'm giving away free servers to every developer who wants to ship faster. Questions, feedback, or roasts — drop them in the comments. I read every one.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>showdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Stop Bidding Against 200 People for $5 Projects — There's a Better Way</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Tue, 10 Mar 2026 03:08:13 +0000</pubDate>
      <link>https://dev.to/yorchperaza/stop-bidding-against-200-people-for-5-projects-theres-a-better-way-3e0n</link>
      <guid>https://dev.to/yorchperaza/stop-bidding-against-200-people-for-5-projects-theres-a-better-way-3e0n</guid>
      <description>&lt;p&gt;If you've spent any time on traditional freelance platforms, you know the drill.&lt;/p&gt;

&lt;p&gt;You find a project that fits your skills perfectly. You spend 30 minutes crafting a thoughtful proposal. You hit submit — and immediately see you're &lt;strong&gt;proposal #47 out of 50 slots&lt;/strong&gt;. Half of them are undercutting you by 80%. The client ghosts everyone and reposts the job two weeks later.&lt;/p&gt;

&lt;p&gt;It's exhausting. And it doesn't have to be this way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing MonkeysWorks — An AI-Powered Freelance Marketplace Built for Developers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://monkeysworks.com" rel="noopener noreferrer"&gt;MonkeysWorks&lt;/a&gt; is a new kind of freelance platform launching soon, and we're opening the doors early for freelancers who want to get in before the crowd.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://monkeysworks.com/join/freelancers" rel="noopener noreferrer"&gt;👉 Join the Prelaunch Waitlist as a Freelancer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes MonkeysWorks Different?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🤖 AI Does the Heavy Lifting
&lt;/h3&gt;

&lt;p&gt;MonkeysWorks uses AI at every step of the process — not as a gimmick, but to genuinely save you time and surface the right opportunities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smart job matching&lt;/strong&gt; that learns your skills, stack, and preferences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-assisted proposals&lt;/strong&gt; so you spend time doing the work, not selling it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated project scoping&lt;/strong&gt; to help clients define what they actually need before they post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No more sifting through 300 irrelevant listings looking for the one real opportunity.&lt;/p&gt;

&lt;h3&gt;
  
  
  💻 Built by Developers, for Developers
&lt;/h3&gt;

&lt;p&gt;MonkeysWorks isn't a generic gig marketplace. It's designed for technical freelancers — developers, DevOps engineers, architects, and technical writers — who build real things and deserve to be compensated accordingly.&lt;/p&gt;

&lt;p&gt;The platform supports the full modern stack and understands the difference between "build me a website" and "I need a Next.js frontend with SSR, integrated with a headless CMS and deployed on GCP."&lt;/p&gt;

&lt;h3&gt;
  
  
  💰 Transparent, Fair Pricing
&lt;/h3&gt;

&lt;p&gt;No race to the bottom. MonkeysWorks is designed to reward quality and expertise, not whoever submits the cheapest bid in the first 10 minutes.&lt;/p&gt;

&lt;p&gt;Clients on the platform are vetted and come ready to pay fair rates for serious work.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Get In Early — Early Freelancers Win
&lt;/h3&gt;

&lt;p&gt;Platforms reward early adopters. Getting in before launch means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Higher visibility&lt;/strong&gt; when clients start posting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority access&lt;/strong&gt; to the first wave of projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Influence&lt;/strong&gt; over platform features — we genuinely listen to early members&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Founding member status&lt;/strong&gt; with perks we're still cooking up&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Who Should Join?
&lt;/h2&gt;

&lt;p&gt;MonkeysWorks is a great fit if you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;full-stack or backend developer&lt;/strong&gt; (PHP, Node, Python, Go, you name it)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;frontend developer&lt;/strong&gt; working with React, Vue, or Next.js&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;DevOps / cloud engineer&lt;/strong&gt; comfortable with GCP, AWS, or containerized workloads&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;technical freelancer&lt;/strong&gt; tired of platforms that treat you like a commodity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're serious about your craft and want to work with clients who respect that — this is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Join
&lt;/h2&gt;

&lt;p&gt;It takes less than a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://monkeysworks.com/join/freelancers" rel="noopener noreferrer"&gt;→ Sign up at monkeysworks.com/join/freelancers&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Leave your info, tell us a bit about your stack, and we'll reach out before the public launch with early access details.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The freelance market isn't broken because there isn't enough work. It's broken because the platforms that connect freelancers to clients were built to maximize transaction volume — not freelancer success.&lt;/p&gt;

&lt;p&gt;MonkeysWorks is being built differently. AI-first, developer-focused, and designed so that the best person for the job actually gets it.&lt;/p&gt;

&lt;p&gt;Come build with us.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://monkeysworks.com/join/freelancers" rel="noopener noreferrer"&gt;Join the MonkeysWorks Prelaunch →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions or want to share feedback? Drop a comment below — the team reads everything.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>MonkeysAI — Live Playground (Development Preview)</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Fri, 13 Feb 2026 04:24:10 +0000</pubDate>
      <link>https://dev.to/yorchperaza/monkeysai-live-playground-development-preview-4152</link>
      <guid>https://dev.to/yorchperaza/monkeysai-live-playground-development-preview-4152</guid>
      <description>&lt;p&gt;We just launched something we've been building quietly inside the Monkeys ecosystem:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://monkeyscms.com/#ai-playground" rel="noopener noreferrer"&gt;MonkeysAI — Live Playground&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn't a wrapper around a third-party API.&lt;/p&gt;

&lt;p&gt;There are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No OpenAI tokens&lt;/li&gt;
&lt;li&gt;No external proxy layer&lt;/li&gt;
&lt;li&gt;No hidden API dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It runs on our own infrastructure.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is MonkeysAI?
&lt;/h2&gt;

&lt;p&gt;MonkeysAI is the AI layer for &lt;a href="https://monkeyscms.com" rel="noopener noreferrer"&gt;MonkeysCMS&lt;/a&gt; and MonkeysLegion — designed to integrate AI directly into content workflows at the architecture level.&lt;/p&gt;

&lt;p&gt;Not as a plugin. Not as a SaaS add-on. &lt;strong&gt;As core infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Live Playground exposes the API directly in the browser so developers can test behavior before integrating it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Can Do in the Playground
&lt;/h2&gt;

&lt;p&gt;🧠 &lt;strong&gt;Generate&lt;/strong&gt;&lt;br&gt;
Create new content from a short brief.&lt;/p&gt;

&lt;p&gt;✨ &lt;strong&gt;Improve&lt;/strong&gt;&lt;br&gt;
Enhance clarity, tone, structure, or formatting of existing content.&lt;/p&gt;

&lt;p&gt;🔎 &lt;strong&gt;Critique &amp;amp; Refine&lt;/strong&gt;&lt;br&gt;
Analyze content and automatically improve weak sections.&lt;/p&gt;




&lt;h2&gt;
  
  
  Smart Model Routing
&lt;/h2&gt;

&lt;p&gt;One of the key pieces we built is &lt;strong&gt;dynamic model selection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of forcing users to choose a model manually, we route intelligently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Fast Mode&lt;/strong&gt; → Optimized for speed and lightweight generation&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;Reasoning Mode&lt;/strong&gt; → Used for deeper analysis and structured logic&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Auto Mode&lt;/strong&gt; → Automatically selects the best model for the task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower cost per request&lt;/li&gt;
&lt;li&gt;Faster response times when complexity isn't required&lt;/li&gt;
&lt;li&gt;Heavy reasoning only when necessary&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Build It This Way?
&lt;/h2&gt;

&lt;p&gt;Most AI integrations today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proxy requests to external APIs&lt;/li&gt;
&lt;li&gt;Add latency&lt;/li&gt;
&lt;li&gt;Lock you into vendor pricing&lt;/li&gt;
&lt;li&gt;Break when token limits change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure control&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Predictable performance&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost transparency&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full integration into CMS-level workflows&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom orchestration&lt;/li&gt;
&lt;li&gt;Model routing logic&lt;/li&gt;
&lt;li&gt;Dedicated servers&lt;/li&gt;
&lt;li&gt;Continuous dataset crawling &amp;amp; validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's slower to build — but architecturally cleaner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Development Preview Notice
&lt;/h2&gt;

&lt;p&gt;This is an early public preview.&lt;/p&gt;

&lt;p&gt;We are actively improving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Response quality&lt;/li&gt;
&lt;li&gt;Crawling verification accuracy&lt;/li&gt;
&lt;li&gt;Performance optimization&lt;/li&gt;
&lt;li&gt;Output stability under load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It's not polished. But it's real.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;The Playground is just step one.&lt;/p&gt;

&lt;p&gt;The goal is deeper AI-native CMS features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inline multilingual optimization (25+ languages)&lt;/li&gt;
&lt;li&gt;Structured content reasoning&lt;/li&gt;
&lt;li&gt;SEO-aware generation&lt;/li&gt;
&lt;li&gt;AI-assisted migrations&lt;/li&gt;
&lt;li&gt;Editorial workflow automation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you're building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A CMS&lt;/li&gt;
&lt;li&gt;An AI product&lt;/li&gt;
&lt;li&gt;A content engine&lt;/li&gt;
&lt;li&gt;Or your own LLM infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd genuinely love feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it. Stress it. Break it. Tell me what to improve.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://monkeyscms.com/#ai-playground" rel="noopener noreferrer"&gt;monkeyscms.com/#ai-playground&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Jorge&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>php</category>
      <category>opensource</category>
    </item>
    <item>
      <title>MonkeysLegion GraphQL 2.0 — A Complete Rewrite, Built for the Future</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Wed, 11 Feb 2026 22:30:05 +0000</pubDate>
      <link>https://dev.to/yorchperaza/monkeyslegion-graphql-20-a-complete-rewrite-built-for-the-future-56ea</link>
      <guid>https://dev.to/yorchperaza/monkeyslegion-graphql-20-a-complete-rewrite-built-for-the-future-56ea</guid>
      <description>&lt;p&gt;When I started working on MonkeysLegion GraphQL, the goal was simple:&lt;br&gt;
&lt;strong&gt;Make GraphQL feel native to modern PHP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Version 2.0 is not an upgrade.&lt;br&gt;
It's a &lt;strong&gt;complete architectural rewrite&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 Repo: &lt;a href="https://github.com/MonkeysCloud/MonkeysLegion-GraphQL" rel="noopener noreferrer"&gt;MonkeysLegion-GraphQL&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Why a Complete Rewrite?
&lt;/h2&gt;

&lt;p&gt;GraphQL in PHP often feels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too coupled to webonyx&lt;/li&gt;
&lt;li&gt;Configuration-heavy&lt;/li&gt;
&lt;li&gt;Reflection-expensive&lt;/li&gt;
&lt;li&gt;Hard to secure properly&lt;/li&gt;
&lt;li&gt;Difficult to scale for real production workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of patching the old system… &lt;strong&gt;I rebuilt it from scratch.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What's New in 2.0
&lt;/h2&gt;
&lt;h3&gt;
  
  
  🧩 Attribute-Driven Schema Definition
&lt;/h3&gt;

&lt;p&gt;No more schema arrays. No more manual wiring.&lt;br&gt;
Everything is powered by &lt;strong&gt;PHP 8.4 attributes&lt;/strong&gt;.&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="na"&gt;#[Type]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserType&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Field]&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;id&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="na"&gt;#[Field]&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;name&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;#[Query]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserQuery&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Field]&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;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&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;Supported attributes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#[Type]&lt;/code&gt; · &lt;code&gt;#[Field]&lt;/code&gt; · &lt;code&gt;#[Query]&lt;/code&gt; · &lt;code&gt;#[Mutation]&lt;/code&gt; · &lt;code&gt;#[Subscription]&lt;/code&gt; · &lt;code&gt;#[Arg]&lt;/code&gt; · &lt;code&gt;#[InputType]&lt;/code&gt; · &lt;code&gt;#[Enum]&lt;/code&gt; · &lt;code&gt;#[InterfaceType]&lt;/code&gt; · &lt;code&gt;#[UnionType]&lt;/code&gt; · &lt;code&gt;#[Middleware]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Clean. Typed. Explicit.&lt;/p&gt;




&lt;h3&gt;
  
  
  ⚙️ New Architecture (Builder Pipeline)
&lt;/h3&gt;

&lt;p&gt;The entire schema generation system is now modular:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SchemaBuilder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TypeBuilder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FieldBuilder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;InputTypeBuilder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EnumBuilder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ArgumentBuilder&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No magic factory classes. No static schema definitions. Just a &lt;strong&gt;clean build pipeline&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧠 GraphQLContext (Immutable Execution Context)
&lt;/h3&gt;

&lt;p&gt;Every request now gets an immutable execution context — HTTP request, authenticated user, DI container, and DataLoaderRegistry:&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;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GraphQLContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Predictable. Safe. Testable.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚀 Performance: DataLoader + Batch Execution
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DataLoader&lt;/strong&gt; — per-request caching + batching. No more N+1 query nightmares.&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="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;abstract&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;batchLoad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$keys&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Per-request caching&lt;/li&gt;
&lt;li&gt;Batch loading&lt;/li&gt;
&lt;li&gt;Registry-based resolution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;BatchExecutor&lt;/strong&gt; — supports multiple operations in one request. Useful for complex clients, microfrontend architectures, and optimized API gateways.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔒 Security Built-In
&lt;/h3&gt;

&lt;p&gt;Security is not optional anymore. MonkeysLegion GraphQL 2.0 includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DepthLimiter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ComplexityAnalyzer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IntrospectionControl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PersistedQueries&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RateLimiter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Production-ready from day one.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🧱 Custom Scalars Included
&lt;/h3&gt;

&lt;p&gt;Out of the box support for: &lt;code&gt;DateTime&lt;/code&gt; · &lt;code&gt;JSON&lt;/code&gt; · &lt;code&gt;Email&lt;/code&gt; · &lt;code&gt;URL&lt;/code&gt; · &lt;code&gt;Upload&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;No need to reimplement common scalars.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔄 Subscriptions (Modern Protocol)
&lt;/h3&gt;

&lt;p&gt;WebSocket layer completely rewritten.&lt;/p&gt;

&lt;p&gt;❌ Removed &lt;code&gt;subscriptions-transport-ws&lt;/code&gt;&lt;br&gt;
✅ Now using &lt;code&gt;graphql-ws&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;New components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SubscriptionServer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SubscriptionManager&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PubSubInterface&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;InMemoryPubSub&lt;/code&gt; / &lt;code&gt;RedisPubSub&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WsAuthenticator&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Removed heavy dependencies like Ratchet/React. &lt;strong&gt;Cleaner. Faster. Modern.&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  🗂 Schema Caching (PSR-16)
&lt;/h3&gt;

&lt;p&gt;Reflection is expensive. So we cache it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SchemaCache&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SchemaCacheWarmer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CLI commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php mlc graphql:cache:warm
php mlc graphql:cache:clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Zero-runtime schema rebuilds in production.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🧾 Input Validation System
&lt;/h3&gt;

&lt;p&gt;Built-in validation layer with &lt;code&gt;InputValidator&lt;/code&gt; and &lt;code&gt;RuleSet&lt;/code&gt;. Structured validation errors via &lt;code&gt;ValidationError&lt;/code&gt; and &lt;code&gt;AuthorizationError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No need to plug external validation unless you want to.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔌 PSR-15 Middleware Support
&lt;/h3&gt;

&lt;p&gt;Fully PSR-15 compatible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GraphQLMiddleware&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GraphiQLMiddleware&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UploadMiddleware&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop into any modern PHP stack.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧬 Entity Integration
&lt;/h3&gt;

&lt;p&gt;Native support for mapping domain entities via &lt;code&gt;EntityTypeMapper&lt;/code&gt; and &lt;code&gt;EntityResolver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Makes MonkeysLegion's entity system work &lt;strong&gt;seamlessly&lt;/strong&gt; with GraphQL.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔗 Relay Pagination Support
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ConnectionType&lt;/code&gt; · &lt;code&gt;EdgeType&lt;/code&gt; · &lt;code&gt;PageInfoType&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ready for modern frontend clients.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧰 CLI Tooling
&lt;/h3&gt;

&lt;p&gt;Built-in CLI commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;graphql:schema:dump&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;graphql:schema:validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;graphql:cache:warm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;graphql:cache:clear&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;graphql:introspect&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GraphQL now feels like a &lt;strong&gt;first-class framework citizen&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Breaking Changes
&lt;/h2&gt;

&lt;p&gt;This is version 2.0 for a reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changed:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Before → After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minimum PHP version&lt;/td&gt;
&lt;td&gt;→ &lt;strong&gt;8.4&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSocket protocol&lt;/td&gt;
&lt;td&gt;→ &lt;strong&gt;graphql-ws&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;→ &lt;strong&gt;.mlc files&lt;/strong&gt; (typed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Types&lt;/td&gt;
&lt;td&gt;No longer extend webonyx base classes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Removed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Schema\SchemaFactory&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Support\Scanner&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Execution\Executor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebSocket\WsHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ratchet/React dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything replaced with cleaner architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;This release is about &lt;strong&gt;performance&lt;/strong&gt;, &lt;strong&gt;security&lt;/strong&gt;, &lt;strong&gt;modern PHP design&lt;/strong&gt;, &lt;strong&gt;clean architecture&lt;/strong&gt;, and &lt;strong&gt;production-readiness&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's not just "GraphQL working in PHP."&lt;br&gt;
It's GraphQL &lt;strong&gt;designed for serious applications&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Federation support&lt;/li&gt;
&lt;li&gt;Advanced query cost modeling&lt;/li&gt;
&lt;li&gt;Observability hooks&lt;/li&gt;
&lt;li&gt;Native OpenTelemetry integration&lt;/li&gt;
&lt;li&gt;Extended subscription drivers&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion GraphQL 2.0 is the foundation for everything I'm building next — AI-native CMS (MonkeysCMS), scalable SaaS APIs, real-time systems, and high-performance microservices.&lt;/p&gt;

&lt;p&gt;Rewriting it was painful. But worth it.&lt;/p&gt;

&lt;p&gt;If you're building serious PHP applications and want modern GraphQL done right:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/MonkeysCloud/MonkeysLegion-GraphQL" rel="noopener noreferrer"&gt;MonkeysLegion-GraphQL on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>graphql</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>MonkeysLegion: Ship Production-Ready PHP in Minutes, Not Days 🚀</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Wed, 28 Jan 2026 23:51:09 +0000</pubDate>
      <link>https://dev.to/yorchperaza/monkeyslegion-ship-production-ready-php-in-minutes-not-days-45jh</link>
      <guid>https://dev.to/yorchperaza/monkeyslegion-ship-production-ready-php-in-minutes-not-days-45jh</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; MonkeysLegion is a lightweight, modular PHP framework that bundles everything you need—blazing-fast router, DI container, CLI tools, and zero-config Docker—so you can focus on features instead of plumbing. It's MIT-licensed, open-source, and we're looking for collaborators!&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Another PHP Framework?
&lt;/h2&gt;

&lt;p&gt;Let's be honest: setting up a new PHP project can feel like assembling IKEA furniture without instructions. You need routing, dependency injection, database migrations, authentication, validation, caching... and by the time you've wired it all together, you've lost the motivation to build the actual thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MonkeysLegion changes that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We built it because we were tired of the boilerplate. We wanted something that's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Fast to start&lt;/strong&gt; — One composer command, and you're coding&lt;/li&gt;
&lt;li&gt;🧩 &lt;strong&gt;Modular&lt;/strong&gt; — Use only what you need&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;Production-ready&lt;/strong&gt; — Built-in auth, validation, caching, and observability&lt;/li&gt;
&lt;li&gt;📖 &lt;strong&gt;Well-documented&lt;/strong&gt; — Because code without docs is just legacy waiting to happen&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Started in 60 Seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project monkeyscloud/monkeyslegion-skeleton my-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
php ml key:generate
composer serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt; — you're live. 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the Box?
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion isn't just another micro-framework. It's a complete ecosystem:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;What You Get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP Stack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PSR-7/15 compliant, middleware pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Attribute-based, auto-discovery, caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Query Builder, Micro-ORM, Migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JWT, RBAC, 2FA, OAuth, API Keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Validation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DTO binding with attribute constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Caching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;File, Redis, Memcached drivers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Migrations, scaffolding, Tinker REPL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Telemetry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prometheus metrics, distributed tracing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Code That Speaks for Itself
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Attribute-Based Routing
&lt;/h3&gt;

&lt;p&gt;No more giant route files. Define routes right where they belong:&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;MonkeysLegion\Router\Attributes\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MonkeysLegion\Router\Attributes\RoutePrefix&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MonkeysLegion\Router\Attributes\Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[RoutePrefix('/api/users')]&lt;/span&gt;
&lt;span class="na"&gt;#[Middleware(['cors', 'throttle'])]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Route('GET', '/', name: 'users.index')]&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;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Response&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;json&lt;/span&gt;&lt;span class="p"&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;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('GET', '/{id:\d+}', name: 'users.show')]&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;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&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;json&lt;/span&gt;&lt;span class="p"&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('POST', '/', name: 'users.create')]&lt;/span&gt;
    &lt;span class="na"&gt;#[Middleware('auth')]&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CreateUserRequest&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;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Validated DTO is automatically injected&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="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&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;json&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="mi"&gt;201&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;h3&gt;
  
  
  Validation with DTOs
&lt;/h3&gt;

&lt;p&gt;Type-safe request validation using PHP attributes:&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;MonkeysLegion\Validation\Attributes&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateUserRequest&lt;/span&gt;
&lt;span class="p"&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="na"&gt;#[Assert\NotBlank]&lt;/span&gt;
        &lt;span class="na"&gt;#[Assert\Email]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="na"&gt;#[Assert\NotBlank]&lt;/span&gt;
        &lt;span class="na"&gt;#[Assert\Length(min: 8, max: 64)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="na"&gt;#[Assert\Url]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$website&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="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;Invalid requests automatically return structured errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Value must be a valid e-mail."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Minimum length is 8 characters."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fluent Query Builder
&lt;/h3&gt;

&lt;p&gt;Write readable database queries without raw SQL:&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;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$qb&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;from&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;leftJoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'profiles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'p.user_id'&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;'users.id'&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;select&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="s1"&gt;'p.avatar'&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&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;'active'&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;whereIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'role'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'editor'&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;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DESC'&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;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;perPage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Built-in Authentication
&lt;/h3&gt;

&lt;p&gt;JWT auth with 2FA support, ready to use:&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;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$auth&lt;/span&gt;&lt;span class="o"&gt;-&amp;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;$email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$password&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;ip&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;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;requires2FA&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="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'requires_2fa'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'challenge'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;challengeToken&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="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'access_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refreshToken&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;
  
  
  Powerful CLI Tools
&lt;/h3&gt;

&lt;p&gt;Scaffold your app without leaving the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php ml make:entity User          &lt;span class="c"&gt;# Generate Entity&lt;/span&gt;
php ml make:controller User      &lt;span class="c"&gt;# Generate Controller&lt;/span&gt;
php ml make:migration            &lt;span class="c"&gt;# Generate migration from entity diff&lt;/span&gt;
php ml migrate                   &lt;span class="c"&gt;# Run migrations&lt;/span&gt;
php ml tinker                    &lt;span class="c"&gt;# Interactive REPL&lt;/span&gt;
php ml openapi:export            &lt;span class="c"&gt;# Export OpenAPI spec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Package Ecosystem
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is built as independent packages. Use the full stack or pick what you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-core&lt;/code&gt; — Kernel, events, helpers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-router&lt;/code&gt; — Full-featured routing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-auth&lt;/code&gt; — JWT, RBAC, 2FA, OAuth&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-query&lt;/code&gt; — Query Builder &amp;amp; ORM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-cache&lt;/code&gt; — PSR-16 multi-driver cache&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-files&lt;/code&gt; — File storage, chunked uploads, S3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-mail&lt;/code&gt; — SMTP, templates, DKIM, queues&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-i18n&lt;/code&gt; — Internationalization&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-telemetry&lt;/code&gt; — Metrics &amp;amp; tracing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-validation&lt;/code&gt; — DTO validation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monkeyslegion-template&lt;/code&gt; — MLView templating engine&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PHP 8.4+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL 8.4&lt;/strong&gt; (recommended)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composer 2.x&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  We Need You! 🤝
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is open-source and MIT-licensed. We're actively looking for collaborators who want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐛 Report bugs and suggest features&lt;/li&gt;
&lt;li&gt;📝 Improve documentation&lt;/li&gt;
&lt;li&gt;🧪 Write tests&lt;/li&gt;
&lt;li&gt;💡 Build new packages&lt;/li&gt;
&lt;li&gt;🌍 Help with translations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No contribution is too small.&lt;/strong&gt; Whether it's fixing a typo or implementing a new feature, we'd love to have you on board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Join Our Community
&lt;/h2&gt;

&lt;p&gt;We've set up a Slack workspace where you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask questions and get help&lt;/li&gt;
&lt;li&gt;Discuss features and architecture&lt;/li&gt;
&lt;li&gt;Share what you're building&lt;/li&gt;
&lt;li&gt;Connect with other developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://join.slack.com/t/monkeyslegion/shared_invite/zt-36jut3kqo-WCwOabVrVrhHBln4xhMATA" rel="noopener noreferrer"&gt;Join MonkeysLegion on Slack&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://monkeyslegion.com" rel="noopener noreferrer"&gt;monkeyslegion.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://monkeyslegion.com/docs/starter" rel="noopener noreferrer"&gt;monkeyslegion.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/MonkeysCloud/MonkeysLegion-Skeleton" rel="noopener noreferrer"&gt;MonkeysCloud/MonkeysLegion-Skeleton&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Packagist:&lt;/strong&gt; &lt;a href="https://packagist.org/packages/monkeyscloud/monkeyslegion-skeleton" rel="noopener noreferrer"&gt;monkeyscloud/monkeyslegion-skeleton&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Started Now
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project monkeyscloud/monkeyslegion-skeleton my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give it a ⭐ on GitHub if you like what you see!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with 💚 by developers who believe PHP deserves better tooling.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What do you think?&lt;/strong&gt; Have you tried MonkeysLegion? What features would you like to see? Drop a comment below or join us on Slack! 👇&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>ai</category>
      <category>laravel</category>
    </item>
    <item>
      <title>🧠 MonkeysCMS: An AI-Native CMS Built on MonkeysLegion — Rethinking Content Platforms from the Core</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Wed, 07 Jan 2026 02:12:47 +0000</pubDate>
      <link>https://dev.to/yorchperaza/monkeyscms-an-ai-native-cms-built-on-monkeyslegion-rethinking-content-platforms-from-the-core-3dpj</link>
      <guid>https://dev.to/yorchperaza/monkeyscms-an-ai-native-cms-built-on-monkeyslegion-rethinking-content-platforms-from-the-core-3dpj</guid>
      <description>&lt;p&gt;Most CMS platforms are built on top of frameworks they don't control.&lt;/p&gt;

&lt;p&gt;That single fact explains why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extensibility feels bolted-on&lt;/li&gt;
&lt;li&gt;Performance tuning is painful&lt;/li&gt;
&lt;li&gt;AI integration feels awkward&lt;/li&gt;
&lt;li&gt;Developer experience degrades over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MonkeysCMS exists because we chose a different path.&lt;/p&gt;

&lt;p&gt;Instead of adapting a CMS to a framework,&lt;br&gt;
we built a framework specifically to power a modern, AI-native CMS.&lt;/p&gt;

&lt;p&gt;That framework is &lt;strong&gt;MonkeysLegion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 CMS repo: &lt;a href="https://github.com/MonkeysCloud/MonkeysCMS" rel="noopener noreferrer"&gt;https://github.com/MonkeysCloud/MonkeysCMS&lt;/a&gt;&lt;br&gt;&lt;br&gt;
👉 Framework ecosystem: &lt;a href="https://github.com/MonkeysCloud" rel="noopener noreferrer"&gt;https://github.com/MonkeysCloud&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🚨 Why Existing CMS Architectures Hit a Ceiling
&lt;/h2&gt;

&lt;p&gt;Modern content systems must handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Headless &amp;amp; hybrid rendering&lt;/li&gt;
&lt;li&gt;API-first delivery&lt;/li&gt;
&lt;li&gt;Multi-channel publishing&lt;/li&gt;
&lt;li&gt;Automation-heavy workflows&lt;/li&gt;
&lt;li&gt;AI-assisted content creation&lt;/li&gt;
&lt;li&gt;Long-term maintainability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yet most CMS platforms fall into one of these traps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ Legacy CMS (Drupal, WordPress, etc.)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Powerful but heavy&lt;/li&gt;
&lt;li&gt;Tight coupling everywhere&lt;/li&gt;
&lt;li&gt;AI added as plugins&lt;/li&gt;
&lt;li&gt;Framework constraints leak into CMS logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ Modern SaaS Headless CMS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Closed source&lt;/li&gt;
&lt;li&gt;Usage-based pricing explosions&lt;/li&gt;
&lt;li&gt;Limited extensibility&lt;/li&gt;
&lt;li&gt;No control over core behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The missing piece?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A CMS built on a framework designed explicitly for content systems and automation.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🐒 Enter MonkeysLegion: The Foundation
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is a modern, modular backend framework built with one goal:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give developers full control over architecture without sacrificing productivity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight&lt;/li&gt;
&lt;li&gt;Explicit&lt;/li&gt;
&lt;li&gt;Modular&lt;/li&gt;
&lt;li&gt;Framework-agnostic&lt;/li&gt;
&lt;li&gt;Built for APIs, automation, and extensibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MonkeysCMS is not using MonkeysLegion accidentally —&lt;br&gt;
it is a direct expression of its design philosophy.&lt;/p&gt;


&lt;h2&gt;
  
  
  🏗️ Why Build a CMS on Its Own Framework?
&lt;/h2&gt;

&lt;p&gt;Because CMSs are not just CRUD apps.&lt;/p&gt;

&lt;p&gt;They need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong domain modeling&lt;/li&gt;
&lt;li&gt;Pluggable logic layers&lt;/li&gt;
&lt;li&gt;Predictable extension points&lt;/li&gt;
&lt;li&gt;Performance control&lt;/li&gt;
&lt;li&gt;AI integration at the core&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MonkeysLegion provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧱 Modular packages (Router, Auth, Cache, Query, CLI, etc.)&lt;/li&gt;
&lt;li&gt;🔌 Clean dependency boundaries&lt;/li&gt;
&lt;li&gt;🧠 Explicit data flow (no magic globals)&lt;/li&gt;
&lt;li&gt;🧪 Testable components by default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lets MonkeysCMS stay:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean&lt;/li&gt;
&lt;li&gt;Maintainable&lt;/li&gt;
&lt;li&gt;Extensible without hacks&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🧩 Architecture Overview: CMS + Framework + AI
&lt;/h2&gt;

&lt;p&gt;Think of the system like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐
│   Frontend  │  (Next.js, Astro, Svelte, Apps)
└─────▲───────┘
      │ API
┌─────┴───────┐
│  MonkeysCMS │  Content, Schemas, Workflows
└─────▲───────┘
      │ Core Services
┌─────┴───────┐
│ MonkeysLegion│  Routing, Auth, Storage, CLI
└─────▲───────┘
      │ Intelligence
┌─────┴───────┐
│  MonkeysAI  │  AI workflows &amp;amp; automation
└─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has one responsibility — and nothing leaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 MonkeysAI: AI Where It Actually Belongs
&lt;/h2&gt;

&lt;p&gt;Because MonkeysCMS is built on MonkeysLegion, AI isn't a hack.&lt;/p&gt;

&lt;p&gt;MonkeysAI integrates cleanly into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content pipelines&lt;/li&gt;
&lt;li&gt;Workflow automation&lt;/li&gt;
&lt;li&gt;Schema interpretation&lt;/li&gt;
&lt;li&gt;Semantic enrichment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context-aware content suggestions&lt;/li&gt;
&lt;li&gt;AI-assisted editing (not replacement)&lt;/li&gt;
&lt;li&gt;Automated content operations&lt;/li&gt;
&lt;li&gt;Future schema-aware generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI becomes a system capability, not a plugin checkbox.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Developer Experience Is a First-Class Feature
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion enforces discipline that benefits MonkeysCMS developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No hidden container magic&lt;/li&gt;
&lt;li&gt;No global state abuse&lt;/li&gt;
&lt;li&gt;Clear service contracts&lt;/li&gt;
&lt;li&gt;Predictable lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a contributor, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can understand modules in isolation&lt;/li&gt;
&lt;li&gt;You don't need to learn "CMS magic"&lt;/li&gt;
&lt;li&gt;Your changes don't break unrelated systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I hate fighting the framework just to add a feature"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This stack was built for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Modular by Nature (Not by Marketing)
&lt;/h2&gt;

&lt;p&gt;Because MonkeysCMS inherits MonkeysLegion's modularity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every CMS feature is a module&lt;/li&gt;
&lt;li&gt;AI capabilities are pluggable&lt;/li&gt;
&lt;li&gt;APIs are versionable&lt;/li&gt;
&lt;li&gt;Integrations are isolated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom deployments easy&lt;/li&gt;
&lt;li&gt;Enterprise use viable&lt;/li&gt;
&lt;li&gt;Long-term maintenance realistic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No forks. No hacks. No rewrites.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🌍 Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;This architecture is ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-driven content platforms&lt;/li&gt;
&lt;li&gt;SaaS documentation hubs&lt;/li&gt;
&lt;li&gt;Multi-brand editorial systems&lt;/li&gt;
&lt;li&gt;Investor/startup platforms&lt;/li&gt;
&lt;li&gt;Knowledge bases &amp;amp; research portals&lt;/li&gt;
&lt;li&gt;CMS + automation hybrids&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anywhere content needs to be &lt;strong&gt;structured, automated, and intelligent.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧑‍💻 Contributing: CMS, Framework, or AI
&lt;/h2&gt;

&lt;p&gt;You don't need to contribute to everything.&lt;/p&gt;

&lt;p&gt;You can help with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MonkeysCMS features&lt;/li&gt;
&lt;li&gt;MonkeysLegion core packages&lt;/li&gt;
&lt;li&gt;MonkeysAI workflows&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;DX improvements&lt;/li&gt;
&lt;li&gt;Integration examples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is an ecosystem — not a monolith.&lt;/p&gt;

&lt;p&gt;👉 Start here: &lt;a href="https://github.com/MonkeysCloud/MonkeysCMS" rel="noopener noreferrer"&gt;https://github.com/MonkeysCloud/MonkeysCMS&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛣️ What's Ahead
&lt;/h2&gt;

&lt;p&gt;Roadmap highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visual schema builders&lt;/li&gt;
&lt;li&gt;AI-powered content ops&lt;/li&gt;
&lt;li&gt;Plugin marketplace&lt;/li&gt;
&lt;li&gt;Advanced workflow automation&lt;/li&gt;
&lt;li&gt;Multi-tenant support&lt;/li&gt;
&lt;li&gt;Performance-focused deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All driven by MonkeysLegion's stability and AI-native design.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Final Thought
&lt;/h2&gt;

&lt;p&gt;MonkeysCMS is not "just another CMS".&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A CMS powered by its own framework&lt;/li&gt;
&lt;li&gt;An AI-native content platform&lt;/li&gt;
&lt;li&gt;A long-term open-source ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Framework first. CMS second. AI everywhere it makes sense.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If this vision resonates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ Star the repo&lt;/li&gt;
&lt;li&gt;🧠 Join the discussions&lt;/li&gt;
&lt;li&gt;🧑‍💻 Build with us&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's build the CMS — and the foundation beneath it — the right way 🐒🚀&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>symfony</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
