<?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: Tomás Alegre Sepúlveda</title>
    <description>The latest articles on DEV Community by Tomás Alegre Sepúlveda (@alpha018).</description>
    <link>https://dev.to/alpha018</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%2F2032745%2F7d24f6ba-283c-402f-826d-e73a9318f3a7.jpeg</url>
      <title>DEV Community: Tomás Alegre Sepúlveda</title>
      <link>https://dev.to/alpha018</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alpha018"/>
    <language>en</language>
    <item>
      <title>🤖 8 Agent Skills and my first MCP server published to npm</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Sun, 06 Sep 2026 03:26:20 +0000</pubDate>
      <link>https://dev.to/alpha018/8-agent-skills-and-my-first-mcp-server-published-to-npm-3fmi</link>
      <guid>https://dev.to/alpha018/8-agent-skills-and-my-first-mcp-server-published-to-npm-3fmi</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/alpha018/8-agent-skills-y-mi-primer-servidor-mcp-publicado-en-npm-4jc9"&gt;🇪🇸 Leer este post en Español&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I spent months watching my agent re-solve the exact same problems, over and over, because I never sat down and wrote them up once so anyone else could reuse them. That's the kind of technical debt nobody ever puts on a roadmap. So I published &lt;code&gt;alpha-skills&lt;/code&gt;: &lt;strong&gt;eight installable Agent Skills and my first MCP server on npm&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the published skills live
&lt;/h2&gt;

&lt;p&gt;The installable catalog lives in &lt;code&gt;skills/&lt;/code&gt;, split into three categories: &lt;strong&gt;&lt;code&gt;external/&lt;/code&gt;&lt;/strong&gt; for third-party APIs, &lt;strong&gt;&lt;code&gt;local/&lt;/code&gt;&lt;/strong&gt; for homelab and workflows, and &lt;strong&gt;&lt;code&gt;general/&lt;/code&gt;&lt;/strong&gt; for cross-project utilities. All three are public: one skill in &lt;code&gt;external/&lt;/code&gt;, one in &lt;code&gt;local/&lt;/code&gt;, and six in &lt;code&gt;general/&lt;/code&gt;. &lt;code&gt;local/&lt;/code&gt; describes the use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skills/
├── external/
│   └── nextdns-api/SKILL.md
├── local/
│   └── progressive-search/SKILL.md
└── general/
    ├── agent-context-generator/SKILL.md
    ├── nestjs-iam-patterns/SKILL.md
    ├── nestjs-advanced-patterns/SKILL.md
    ├── nestjs-graphql/SKILL.md
    ├── tuning-claude-code/SKILL.md
    └── obsidian-second-brain/SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;DIAGRAM 02: 02-public-skills-structure-en.png&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The eight skills
&lt;/h2&gt;

&lt;p&gt;Three categories: &lt;code&gt;external/&lt;/code&gt; for third-party services, &lt;code&gt;local/&lt;/code&gt; for homelab infrastructure and personal workflows, &lt;code&gt;general/&lt;/code&gt; for cross-cutting utilities that don't depend on any one service.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nextdns-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;external&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NextDNS API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;progressive-search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;local&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Code and documentation search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent-context-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nestjs-iam-patterns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authentication and permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nestjs-advanced-patterns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NestJS internals and architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nestjs-graphql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Code-first and schema-first GraphQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tuning-claude-code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claude Code configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obsidian-second-brain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Note organization and review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fewckqcibc2cmofssbtld.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fewckqcibc2cmofssbtld.png" alt="The eight alpha-skills grouped by category, with the NextDNS MCP server as an additional component" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each command installs one skill. Run the command for the one you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;nextdns-api&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A full reference for the NextDNS REST API: profiles, security/privacy/parental-control settings, denylist and allowlist management, analytics, query logs. This is the one the MCP server below is built directly against.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; nextdns-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;progressive-search&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The discovery workflow I run constantly: code questions go to CodeGraph, doc questions go to QMD, and it falls back to grep/find when neither answers. It keeps an in-session graph of what's already been found so it never repeats a search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; progressive-search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;code&gt;agent-context-generator&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Generates, refreshes, or audits a project's &lt;code&gt;CLAUDE.md&lt;/code&gt;/&lt;code&gt;AGENTS.md&lt;/code&gt;, grounded in what the repo actually contains: real dependencies, real folder structure, real scripts. Not a generic template that assumes a stack you don't have.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; agent-context-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;code&gt;nestjs-iam-patterns&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Auth and authz patterns for NestJS: JWT access/refresh, session auth with Passport and Redis, API keys, RBAC, granular permissions, ABAC, Google Sign-In, TOTP/2FA. Led by a decision guide for which pattern fits your actual situation, not a catalog of all of them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; nestjs-iam-patterns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;code&gt;nestjs-advanced-patterns&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Advanced NestJS internals: explicit and implicit DI tokens, dynamic modules, runtime discovery, durable providers, worker threads, circuit breaker, WebSocket gateways, and a decision table for picking a microservice transporter across TCP, Redis, MQTT, NATS, RabbitMQ, Kafka, gRPC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; nestjs-advanced-patterns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. &lt;code&gt;nestjs-graphql&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;One skill covering both code-first and schema-first GraphQL in NestJS, with a decision table that routes every task to exactly one reference file so the agent never loads both halves at once. It covers subscriptions with PubSub/Redis, the N+1 problem solved with DataLoader, typed guards, query depth and cost limits, and a production-hardening section verified against Apollo Server's actual source, not against what I remembered.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; nestjs-graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. &lt;code&gt;tuning-claude-code&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Designs the whole Claude Code configuration stack for a repo: &lt;code&gt;CLAUDE.md&lt;/code&gt; treated as a token budget, path-scoped rules, custom subagents, deterministic hooks, a pruned MCP list, parallel worktrees, and headless CI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; tuning-claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. &lt;code&gt;obsidian-second-brain&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A decision guide for running an Obsidian vault as a knowledge system that actually survives contact with real use. It steers away from productivity theater and plugin overload, picks one lead organizer (PARA folders for delivery work, Zettelkasten links for idea generation), and states plainly when Obsidian is the wrong tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; obsidian-second-brain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The style these skills share
&lt;/h2&gt;

&lt;p&gt;None of the &lt;code&gt;general/&lt;/code&gt; skills started from a written style guide. But by the fourth one, the pattern was impossible to miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decision guide, not feature list.&lt;/strong&gt; The skill doesn't dump everything it knows. It opens with a table or a short procedure that picks the right option for your actual situation, and says plainly when the tool is the wrong choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive disclosure.&lt;/strong&gt; &lt;code&gt;SKILL.md&lt;/code&gt; stays a lean procedure (under 500 lines). Deep material moves to &lt;code&gt;references/*.md&lt;/code&gt;, and the body tells the agent exactly when to load each one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger-oriented descriptions, with evals.&lt;/strong&gt; The &lt;code&gt;description&lt;/code&gt; frontmatter field is the highest-leverage one: a vague one means the skill silently never fires. Every skill ships an &lt;code&gt;evals/trigger-eval.json&lt;/code&gt; with should-trigger and should-not-trigger cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain ASCII, no AI tics.&lt;/strong&gt; Every file that gets persisted goes through a de-AI pass before it lands.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The first MCP server
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@alpha018/nextdns-mcp&lt;/code&gt; is a stdio MCP server, run through &lt;code&gt;npx&lt;/code&gt;, that exposes the NextDNS API: reading and updating a profile's settings, managing list entries, pulling analytics, reading logs. It's built on the same reference doc that backs &lt;code&gt;nextdns-api&lt;/code&gt;, so the two stay in sync on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing to npm: the trusted publishing problem
&lt;/h2&gt;

&lt;p&gt;This is the part that cost me more than ten CI commits to get right.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@semantic-release/npm&lt;/code&gt; doesn't support OIDC trusted publishing. Its &lt;code&gt;verifyConditions&lt;/code&gt; step demands an &lt;code&gt;NPM_TOKEN&lt;/code&gt; or a preconfigured &lt;code&gt;.npmrc&lt;/code&gt; no matter what &lt;code&gt;id-token: write&lt;/code&gt; is set to in the workflow. That permission only buys provenance signing through the plugin — not authentication.&lt;/p&gt;

&lt;p&gt;What actually worked is two shared workflows, generalized across every package under &lt;code&gt;mcp-servers/*&lt;/code&gt; instead of one file pair per package:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Validate packages on every PR
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Workflow:&lt;/strong&gt; &lt;code&gt;pr-test-mcp-servers.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Detects which package directories changed and runs &lt;strong&gt;lint, typecheck, build and test&lt;/strong&gt; for each package, across &lt;strong&gt;Node 22 and 24&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Version and publish from main
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Workflow:&lt;/strong&gt; &lt;code&gt;build-publish-mcp-servers.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On every push to &lt;code&gt;main&lt;/code&gt;, for each changed package, within a single job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;strong&gt;build and test&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npx semantic-release&lt;/code&gt; with &lt;code&gt;npmPublish: false&lt;/code&gt;: update the version, changelog, tag and release, without requiring npm authentication.&lt;/li&gt;
&lt;li&gt;Compare the freshly updated local version with &lt;code&gt;npm view &amp;lt;pkg&amp;gt; version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If they differ, run &lt;code&gt;npm publish --provenance&lt;/code&gt; directly through the npm CLI.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The split that solved it:&lt;/strong&gt; semantic-release handles versioning; the npm CLI handles publishing through OIDC.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5xnmuxeyt0911ryp4dqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5xnmuxeyt0911ryp4dqx.png" alt="MCP publishing flow: PR checks, versioning with semantic-release, and npm publishing through OIDC when the version changes" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Four gotchas that cost real time
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The first publish is manual.&lt;/strong&gt; A brand-new npm package can't bootstrap through OIDC under any circumstances. The first version has to be published by hand, with a real login, before the Trusted Publisher link can even be added on npmjs.com.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The bin path matters.&lt;/strong&gt; &lt;code&gt;npm publish&lt;/code&gt; silently drops any &lt;code&gt;bin&lt;/code&gt; path starting with &lt;code&gt;./&lt;/code&gt;. &lt;code&gt;"./dist/index.js"&lt;/code&gt; gets dropped with only a warning — write &lt;code&gt;"dist/index.js"&lt;/code&gt; instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Owner casing matters.&lt;/strong&gt; npm's provenance verification checks &lt;code&gt;repository.url&lt;/code&gt; against the OIDC claim's owner case-sensitively. &lt;code&gt;github.com/alpha018/...&lt;/code&gt; failed against the real owner casing, which is &lt;code&gt;Alpha018&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The workflow filename must match.&lt;/strong&gt; npm's Trusted Publisher config pins the exact workflow filename. Rename the file without updating that entry and npm signs the provenance fine, then rejects the publish PUT with an &lt;code&gt;E404&lt;/code&gt; that looks identical, in shape, to "Trusted Publisher not configured".&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When this setup is not worth it
&lt;/h2&gt;

&lt;p&gt;If you're publishing a single package and have no plan to add a second one, this whole scaffold is overengineering. A plain workflow with &lt;code&gt;NPM_TOKEN&lt;/code&gt; in a secret solves the same problem in fifteen minutes, no OIDC required. The two shared workflows only pay for themselves once a second or third package shows up — before that, they're just time you're not spending on the skill that actually matters.&lt;/p&gt;

&lt;p&gt;Adding a second MCP server now is trivial: it only needs its own &lt;code&gt;package.json&lt;/code&gt; and its own &lt;code&gt;.releaserc.json&lt;/code&gt;, and both shared workflows pick it up automatically. Solving this properly once pays off in every package that comes after.&lt;/p&gt;

&lt;p&gt;If any of these eight skills fixes something you've been rewriting every week, the repo is &lt;a href="https://github.com/Alpha018/alpha-skills" rel="noopener noreferrer"&gt;github.com/Alpha018/alpha-skills&lt;/a&gt;. And if you want the deeper version of any single one, some already have their own extended write-up on &lt;a href="https://buymeacoffee.com/alpha018/i-published-8-skills-first-mcp-server" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading this far — if you try one out, tell me what broke, because something always does. Which of the eight would fix the most for your day-to-day?&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💌 Special Acknowledgments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;To a girl who, without knowing it, pushed me to keep creating and coding in my free time. To the one who was once my spark: for "YLP", with gratitude... and with scars that also teach.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>opensource</category>
      <category>agentskills</category>
      <category>mcp</category>
      <category>npm</category>
    </item>
    <item>
      <title>🤖 8 Agent Skills y mi primer servidor MCP publicado en npm</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Sun, 06 Sep 2026 03:26:02 +0000</pubDate>
      <link>https://dev.to/alpha018/8-agent-skills-y-mi-primer-servidor-mcp-publicado-en-npm-4jc9</link>
      <guid>https://dev.to/alpha018/8-agent-skills-y-mi-primer-servidor-mcp-publicado-en-npm-4jc9</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/alpha018/8-agent-skills-and-my-first-mcp-server-published-to-npm-3fmi"&gt;🇺🇸 Read this post in English&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Llevo meses haciendo que mi agente resuelva los mismos problemas una y otra vez porque nunca me tomé el tiempo de escribirlos una sola vez, bien, y dejar que otros los reusaran. Ese es exactamente el tipo de deuda técnica que nadie pone en un roadmap. Así que publiqué &lt;code&gt;alpha-skills&lt;/code&gt;: &lt;strong&gt;ocho Agent Skills instalables y mi primer servidor MCP en npm&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dónde están las skills publicadas
&lt;/h2&gt;

&lt;p&gt;El catálogo instalable vive en &lt;code&gt;skills/&lt;/code&gt;, separado en tres categorías: &lt;strong&gt;&lt;code&gt;external/&lt;/code&gt;&lt;/strong&gt; para APIs de terceros, &lt;strong&gt;&lt;code&gt;local/&lt;/code&gt;&lt;/strong&gt; para homelab y flujos de trabajo, y &lt;strong&gt;&lt;code&gt;general/&lt;/code&gt;&lt;/strong&gt; para utilidades transversales. Las tres son públicas: una skill en &lt;code&gt;external/&lt;/code&gt;, una en &lt;code&gt;local/&lt;/code&gt; y seis en &lt;code&gt;general/&lt;/code&gt;. &lt;code&gt;local/&lt;/code&gt; describe su ámbito de uso.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skills/
├── external/
│   └── nextdns-api/SKILL.md
├── local/
│   └── progressive-search/SKILL.md
└── general/
    ├── agent-context-generator/SKILL.md
    ├── nestjs-iam-patterns/SKILL.md
    ├── nestjs-advanced-patterns/SKILL.md
    ├── nestjs-graphql/SKILL.md
    ├── tuning-claude-code/SKILL.md
    └── obsidian-second-brain/SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fonfs0lvfw8jzehiyrfk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fonfs0lvfw8jzehiyrfk8.png" alt="Estructura del catálogo público en skills/: una skill en external, una en local y seis en general" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Las ocho skills
&lt;/h2&gt;

&lt;p&gt;Tres categorías: &lt;code&gt;external/&lt;/code&gt; para servicios de terceros, &lt;code&gt;local/&lt;/code&gt; para infraestructura de homelab y workflows propios, &lt;code&gt;general/&lt;/code&gt; para utilidades transversales que no dependen de ningún servicio en particular.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Categoría&lt;/th&gt;
&lt;th&gt;Para qué sirve&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nextdns-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;external&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API de NextDNS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;progressive-search&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;local&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Búsqueda de código y documentación&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent-context-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contexto de proyecto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nestjs-iam-patterns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Autenticación y permisos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nestjs-advanced-patterns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internals y arquitectura de NestJS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nestjs-graphql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GraphQL code-first y schema-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tuning-claude-code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Configuración de Claude Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obsidian-second-brain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;general&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Organización y revisión de notas&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffn7xxjv5blw6pbqbf2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffn7xxjv5blw6pbqbf2j.png" alt="Las ocho skills de alpha-skills agrupadas por categoría, junto al servidor MCP de NextDNS como componente adicional" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cada comando instala una skill. Ejecuta el de la que necesites.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;nextdns-api&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Referencia completa de la API REST de NextDNS: perfiles, seguridad/privacidad/control parental, listas de bloqueo y permitidas, analíticas, logs de consultas. Es la que respalda al MCP server que describo más abajo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; nextdns-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;progressive-search&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;El workflow de descubrimiento que uso todo el tiempo: preguntas de código van a CodeGraph, preguntas de documentación van a QMD, y si ninguno responde cae a grep/find. Mantiene un grafo en sesión de lo ya encontrado para no repetir búsquedas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; progressive-search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;code&gt;agent-context-generator&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Genera, refresca o audita el &lt;code&gt;CLAUDE.md&lt;/code&gt;/&lt;code&gt;AGENTS.md&lt;/code&gt; de un proyecto basado en lo que el repo realmente tiene: dependencias reales, estructura real, scripts reales. No en una plantilla genérica que asume un stack que no existe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; agent-context-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;code&gt;nestjs-iam-patterns&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Patrones de auth/authz en NestJS: JWT access/refresh, sesiones con Passport y Redis, API keys, RBAC, permisos granulares, ABAC, Google Sign-In, TOTP/2FA. Liderada por una guía de decisión: qué patrón le corresponde a tu situación, no un catálogo de todos ellos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; nestjs-iam-patterns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;code&gt;nestjs-advanced-patterns&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Internals avanzados de NestJS: tokens de DI explícitos e implícitos, módulos dinámicos, discovery en runtime, providers durables, worker threads, circuit breaker, WebSocket gateways, y una tabla de decisión para elegir transporter en microservicios (TCP, Redis, MQTT, NATS, RabbitMQ, Kafka, gRPC).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; nestjs-advanced-patterns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. &lt;code&gt;nestjs-graphql&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Una sola skill que cubre code-first y schema-first en NestJS, con una tabla de decisión que enruta cada tarea a exactamente un archivo de referencia para que el agente nunca cargue las dos mitades a la vez. Cubre subscriptions con PubSub/Redis, el problema N+1 resuelto con DataLoader, guards tipados, límites de profundidad y costo de queries, y una sección de hardening de producción verificada contra el código fuente real de Apollo Server, no contra lo que yo recordaba de memoria.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; nestjs-graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. &lt;code&gt;tuning-claude-code&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Diseña todo el stack de configuración de Claude Code para un repo: &lt;code&gt;CLAUDE.md&lt;/code&gt; tratado como presupuesto de tokens, reglas por path, subagentes personalizados, hooks determinísticos, una lista de MCP podada, worktrees paralelos y CI headless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; tuning-claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. &lt;code&gt;obsidian-second-brain&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Guía de decisión para llevar un vault de Obsidian como sistema de conocimiento que efectivamente dura. Evita la teatralidad de productividad y la sobrecarga de plugins, elige un organizador líder (carpetas PARA para trabajo de entrega, enlaces Zettelkasten para generación de ideas) y dice explícitamente cuándo Obsidian no es la herramienta correcta.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add Alpha018/alpha-skills &lt;span class="nt"&gt;-s&lt;/span&gt; obsidian-second-brain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  El estilo que comparten las skills
&lt;/h2&gt;

&lt;p&gt;Ninguna de las skills de &lt;code&gt;general/&lt;/code&gt; se planificó con un manual de estilo desde el día uno. Pero después de la cuarta, el patrón ya era imposible de ignorar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guía de decisión, no lista de features.&lt;/strong&gt; La skill no vuelca todo lo que sabe. Abre con una tabla o un procedimiento corto que elige la opción correcta para tu situación real, y dice sin rodeos cuándo la herramienta no es la indicada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive disclosure.&lt;/strong&gt; El &lt;code&gt;SKILL.md&lt;/code&gt; se mantiene como un procedimiento delgado (bajo 500 líneas). El detalle profundo va a &lt;code&gt;references/*.md&lt;/code&gt;, y el cuerpo le dice al agente exactamente cuándo cargar cada uno.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Descripciones orientadas a trigger, con evals.&lt;/strong&gt; El campo &lt;code&gt;description&lt;/code&gt; del frontmatter es el de mayor apalancamiento: uno vago significa que la skill nunca se activa. Cada skill trae un &lt;code&gt;evals/trigger-eval.json&lt;/code&gt; con casos de should-trigger y should-not-trigger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ASCII plano, sin tics de IA.&lt;/strong&gt; Todo archivo persistido pasa por una revisión anti-IA antes de quedar.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  El primer servidor MCP
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@alpha018/nextdns-mcp&lt;/code&gt; es un servidor MCP stdio, corrido vía &lt;code&gt;npx&lt;/code&gt;, que expone la API de NextDNS: leer y actualizar la configuración de un perfil, gestionar entradas de listas, tirar analíticas, leer logs. Está construido sobre la misma referencia que respalda a &lt;code&gt;nextdns-api&lt;/code&gt;, así que las dos piezas se mantienen sincronizadas a propósito.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publicación en npm: el problema de trusted publishing
&lt;/h2&gt;

&lt;p&gt;Acá está la parte que me costó más de diez commits de CI entender bien.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@semantic-release/npm&lt;/code&gt; no soporta OIDC trusted publishing. Su paso &lt;code&gt;verifyConditions&lt;/code&gt; exige un &lt;code&gt;NPM_TOKEN&lt;/code&gt; o un &lt;code&gt;.npmrc&lt;/code&gt; preconfigurado sin importar qué tenga &lt;code&gt;id-token: write&lt;/code&gt; en el workflow. Ese permiso solo compra firma de provenance a través del plugin, no autenticación.&lt;/p&gt;

&lt;p&gt;La arquitectura que terminó funcionando son dos workflows compartidos, generalizados sobre cualquier paquete en &lt;code&gt;mcp-servers/*&lt;/code&gt; en lugar de un par de archivos por paquete:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Validar los paquetes en cada PR
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Workflow:&lt;/strong&gt; &lt;code&gt;pr-test-mcp-servers.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Detecta qué directorios cambiaron y ejecuta &lt;strong&gt;lint, typecheck, build y test&lt;/strong&gt; para cada paquete, en &lt;strong&gt;Node 22 y 24&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Versionar y publicar desde main
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Workflow:&lt;/strong&gt; &lt;code&gt;build-publish-mcp-servers.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;En cada push a &lt;code&gt;main&lt;/code&gt;, por paquete modificado y dentro de un solo job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ejecutar &lt;strong&gt;build y test&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Ejecutar &lt;code&gt;npx semantic-release&lt;/code&gt; con &lt;code&gt;npmPublish: false&lt;/code&gt;: actualizar versión, changelog, tag y release, sin necesitar autenticación de npm.&lt;/li&gt;
&lt;li&gt;Comparar la versión local recién actualizada con &lt;code&gt;npm view &amp;lt;pkg&amp;gt; version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Si difieren, ejecutar &lt;code&gt;npm publish --provenance&lt;/code&gt; directamente con el CLI de npm.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;La separación que resolvió el problema:&lt;/strong&gt; semantic-release se encarga del versionado; el CLI de npm realiza la publicación con OIDC.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmyn4vpqxlzvx8e956sll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmyn4vpqxlzvx8e956sll.png" alt="Flujo de publicación del MCP: validaciones del PR, versionado con semantic-release y publicación en npm mediante OIDC cuando cambia la versión" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cuatro errores que me costaron tiempo real
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;La primera publicación es manual.&lt;/strong&gt; Un paquete nuevo en npm no puede bootstrapearse vía OIDC bajo ninguna circunstancia. La primera versión hay que publicarla a mano, con login real, antes de que el Trusted Publisher se pueda siquiera vincular en npmjs.com.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;La ruta de bin importa.&lt;/strong&gt; &lt;code&gt;npm publish&lt;/code&gt; descarta en silencio cualquier &lt;code&gt;bin&lt;/code&gt; que empiece con &lt;code&gt;./&lt;/code&gt;. &lt;code&gt;"./dist/index.js"&lt;/code&gt; se cae con solo un warning; hay que escribir &lt;code&gt;"dist/index.js"&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;El owner distingue mayúsculas.&lt;/strong&gt; La verificación de provenance de npm compara &lt;code&gt;repository.url&lt;/code&gt; contra el owner del claim de OIDC de forma sensible a mayúsculas. &lt;code&gt;github.com/alpha018/...&lt;/code&gt; falló contra el casing real del owner, que es &lt;code&gt;Alpha018&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;El nombre del workflow debe coincidir.&lt;/strong&gt; El Trusted Publisher de npm fija el nombre exacto del archivo del workflow. Renombrarlo sin actualizar esa entrada hace que npm firme la provenance sin problema y después rechace el PUT del publish con un &lt;code&gt;E404&lt;/code&gt; que es idéntico, en forma, al de "Trusted Publisher no configurado".&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cuándo esta configuración no compensa
&lt;/h2&gt;

&lt;p&gt;Si publicas un solo paquete y no piensas agregar más, este andamiaje es sobreingeniería. Un workflow simple con &lt;code&gt;NPM_TOKEN&lt;/code&gt; en un secret resuelve lo mismo en quince minutos y sin OIDC. La complejidad de los dos workflows compartidos se paga sola recién cuando aparece un segundo o tercer paquete — antes de eso, es tiempo que le resta a escribir la skill que en realidad importa.&lt;/p&gt;

&lt;p&gt;Agregar un segundo servidor MCP ahora es trivial: solo necesita su propio &lt;code&gt;package.json&lt;/code&gt; y su propio &lt;code&gt;.releaserc.json&lt;/code&gt;, los workflows compartidos lo levantan solos. El trabajo de resolver esto bien una vez se paga en cada paquete nuevo que llega después.&lt;/p&gt;

&lt;p&gt;Si alguna de estas ocho skills te resuelve algo que venías reescribiendo cada semana, el repo es &lt;a href="https://github.com/Alpha018/alpha-skills" rel="noopener noreferrer"&gt;github.com/Alpha018/alpha-skills&lt;/a&gt;. Y si prefieres la versión con más detalle todavía sobre alguna en particular, algunas ya tienen su propio post extendido en &lt;a href="https://buymeacoffee.com/alpha018/publiqu-8-skills-y-mi-primer-mcp-server-para-gente-que-nunca-voy-conocer" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Gracias por leer hasta acá — si pruebas alguna, cuéntame qué se rompió, porque seguro algo se rompe. ¿Cuál de las ocho te resuelve más el día a día?&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💌 Agradecimientos Especiales&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A una chica que, sin saberlo, me empujó a seguir creando y programando en mi tiempo libre. A quien alguna vez fue mi chispa: para “YLP”, con gratitud… y con cicatrices que también enseñan.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>opensource</category>
      <category>agentskills</category>
      <category>mcp</category>
      <category>npm</category>
    </item>
    <item>
      <title>☠️ How to Connect Your Router Directly to Fiber Without Your ISP's Modem (SFP GPON Hack)</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Sat, 25 Apr 2026 21:07:04 +0000</pubDate>
      <link>https://dev.to/alpha018/how-to-connect-your-router-directly-to-fiber-without-your-isps-modem-sfp-gpon-hack-15b2</link>
      <guid>https://dev.to/alpha018/how-to-connect-your-router-directly-to-fiber-without-your-isps-modem-sfp-gpon-hack-15b2</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/alpha018/como-conectar-tu-router-directo-a-la-fibra-de-entel-chile-sin-su-modem-sfp-gpon-hack-19nd"&gt;🇪🇸 Leer este post en Español&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Entel Chile told me bridge mode wasn't available for business customers. A week later, my Ubiquiti router was plugged directly into fiber with no ISP hardware anywhere in the path.&lt;/p&gt;

&lt;p&gt;This post covers how: a Ubiquiti UF-Instant SFP GPON transceiver, UART access, direct flash via U-Boot, and GPON spoofing to make Entel's OLT accept the stick as if it were the original Huawei ONT.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Condensed version.&lt;/strong&gt; This post covers the process without the failed attempts or dead ends. The extended version — kernel panic on the MC220L, three firmware attempts, TFTP recovery — is on &lt;a href="https://buymeacoffee.com/alpha018/how-i-got-rid-my-isp-modem-hacking-sfp-gpon-transceiver" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Legal disclaimer:&lt;/strong&gt; This content is for educational purposes only. Modifying firmware on telecommunications devices may violate your ISP contract and local regulations. Check applicable laws before replicating this process.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem: ISP vendor lock-in
&lt;/h2&gt;

&lt;p&gt;ISPs install their own ONT and refuse bridge mode, forcing you to use their router for PPPoE. If you want your own router, you're stuck with double NAT or bypassing ISP hardware entirely.&lt;/p&gt;

&lt;p&gt;I wanted dual WAN on my Ubiquiti UniFi Gateway Fiber with a static IP from Entel. No double NAT. No Entel modem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&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;Transceiver&lt;/td&gt;
&lt;td&gt;Ubiquiti UFiber UF-Instant (RTL9601CI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;td&gt;Ubiquiti UniFi Gateway Fiber (Dual WAN, SFP+ slot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ISP&lt;/td&gt;
&lt;td&gt;Entel Chile (Huawei OLT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original ONT&lt;/td&gt;
&lt;td&gt;Huawei OptiXstar (cloned for spoofing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Console access&lt;/td&gt;
&lt;td&gt;UART TTL adapter (3.3V — mandatory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TFTP server&lt;/td&gt;
&lt;td&gt;Mac with tftp-now or tftp-hpa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rootfs prep&lt;/td&gt;
&lt;td&gt;Docker (required on macOS)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1byf16a7ihoeoss8i5k.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1byf16a7ihoeoss8i5k.jpeg" alt="UF-Instant alongside the UART TTL adapter — wires soldered to the PCB pads"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6cx02igafiw98rl4peg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6cx02igafiw98rl4peg.jpeg" alt="Full experiment setup — modified transceiver connected to the switch and USB-C hub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnracnn29o2nxcoc7qeql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnracnn29o2nxcoc7qeql.png" alt="UF-Instant pinout reference (Rx, Tx, GND, VCC)"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Source: &lt;a href="https://github.com/stich86/UF-Instant-Mod" rel="noopener noreferrer"&gt;stich86/UF-Instant-Mod&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  UART access: the primary method
&lt;/h2&gt;

&lt;p&gt;Every flash operation runs from here. The UF-Instant exposes UART pads on the PCB (TX, RX, GND). 3.3V adapter, &lt;code&gt;115200 8N1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
screen /dev/tty.usbserial-XXXXX 115200

&lt;span class="c"&gt;# Linux&lt;/span&gt;
screen /dev/ttyUSB0 115200

&lt;span class="c"&gt;# With minicom&lt;/span&gt;
minicom &lt;span class="nt"&gt;-D&lt;/span&gt; /dev/ttyUSB0 &lt;span class="nt"&gt;-b&lt;/span&gt; 115200 &lt;span class="nt"&gt;-8&lt;/span&gt; &lt;span class="nt"&gt;--noinit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Power on the stick with UART connected and you get the full U-Boot output. Press any key in the first few seconds to interrupt boot and land at the &lt;code&gt;9601C#&lt;/code&gt; prompt, where you can flash partitions directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  MTD partition map
&lt;/h2&gt;

&lt;p&gt;The UF-Instant uses a dual-image boot system. The relevant partitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mtd4  → kernel0 / k0 (3MB)   — Image 0 kernel (original Ubiquiti — fallback)
mtd5  → rootfs0 / r0 (6MB)   — Image 0 rootfs (original Ubiquiti firmware)
mtd6  → kernel1 / k1 (3MB)   — Image 1 kernel — cloned kernel goes here
mtd7  → rootfs1 / r1 (4.5MB) — Image 1 rootfs — stich86 MOD goes here
mtd8  → europa.data          — Laser calibration ⚠️ NEVER TOUCH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boot control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check active image&lt;/span&gt;
nv getenv sw_active sw_commit

&lt;span class="c"&gt;# Switch to image 1&lt;/span&gt;
setenv sw_active 1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; setenv sw_commit 1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; saveenv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; boot

&lt;span class="c"&gt;# Fall back to image 0 (original Ubiquiti firmware)&lt;/span&gt;
setenv sw_active 0 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; setenv sw_commit 0 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; saveenv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; boot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The process: straight to the point
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On the host&lt;/strong&gt; (before connecting UART): download the MOD rootfs and Ubiquiti v4.4.2 kernel directly from &lt;a href="https://github.com/stich86/UF-Instant-Mod" rel="noopener noreferrer"&gt;stich86/UF-Instant-Mod&lt;/a&gt; and place them in the TFTP server directory. stich86's squashfs binary flashes as-is — no reprocessing needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When do you need Docker?&lt;/strong&gt; Only if you modify the rootfs (extract, change files, repack). On macOS, &lt;code&gt;mksquashfs&lt;/code&gt; can't recreate device nodes without explicit parameters. For the standard flow with stich86's binary, skip Docker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Via UART, in U-Boot (&lt;code&gt;9601C#&lt;/code&gt;):&lt;/strong&gt; initialize flash, clone the kernel from image 0 to image 1, and flash the MOD rootfs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sf probe 0

&lt;span class="c"&gt;# Clone Ubiquiti v4.4.2 kernel from image 0 → image 1&lt;/span&gt;
sf &lt;span class="nb"&gt;read  &lt;/span&gt;0x81000000 0x50000  0x300000   &lt;span class="c"&gt;# read kernel0 into RAM&lt;/span&gt;
sf erase 0x4e0000 0x300000              &lt;span class="c"&gt;# erase kernel1 (mtd6)&lt;/span&gt;
sf write 0x81000000 0x4e0000 0x300000   &lt;span class="c"&gt;# write to mtd6&lt;/span&gt;

&lt;span class="c"&gt;# Flash MOD rootfs (stich86) via TFTP&lt;/span&gt;
tftpboot 0x81000000 &lt;span class="o"&gt;[&lt;/span&gt;HOST_IP]:[stich86_rootfs_filename]
sf erase 0x7e0000 0x480000              &lt;span class="c"&gt;# erase rootfs1 (mtd7)&lt;/span&gt;
sf write 0x81000000 0x7e0000 &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filesize&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Activate image 1 and boot&lt;/span&gt;
setenv sw_active 1
setenv sw_commit 1
saveenv
boot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Via UART, from Linux&lt;/strong&gt; (after booting image 1, credentials &lt;code&gt;admin&lt;/code&gt;/&lt;code&gt;admin&lt;/code&gt; — some builds use &lt;code&gt;ubnt&lt;/code&gt;/&lt;code&gt;ubnt&lt;/code&gt;): configure GPON identity with the original Huawei ONT data and neutralize the bootlimit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# GPON identity — use your actual Huawei ONT data&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;GPON_SN HWTC[XXXXXXXX]          &lt;span class="c"&gt;# ONT SN: HWTC + 8 hex chars&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;PON_VENDOR_ID HWTC
flash &lt;span class="nb"&gt;set &lt;/span&gt;OMCI_OLT_MODE 1
flash &lt;span class="nb"&gt;set &lt;/span&gt;OMCI_FAKE_OK 1
flash &lt;span class="nb"&gt;set &lt;/span&gt;OMCI_SW_VER1 &lt;span class="o"&gt;[&lt;/span&gt;ONT_SW_VERSION]   &lt;span class="c"&gt;# ONT software version&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;OMCI_SW_VER2 &lt;span class="o"&gt;[&lt;/span&gt;ONT_SW_VERSION]
flash &lt;span class="nb"&gt;set &lt;/span&gt;HW_HWVER &lt;span class="o"&gt;[&lt;/span&gt;ONT_HW_VERSION]       &lt;span class="c"&gt;# ONT hardware version&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;GPON_ONU_MODEL &lt;span class="o"&gt;[&lt;/span&gt;ONT_MODEL]      &lt;span class="c"&gt;# ONT model&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;ELAN_MAC_ADDR &lt;span class="o"&gt;[&lt;/span&gt;MAC_NO_COLONS]   &lt;span class="c"&gt;# ONT MAC without colons&lt;/span&gt;

&lt;span class="c"&gt;# Disable automatic factory reset (bootlimit=10 by default)&lt;/span&gt;
nv setenv bootcount 0
nv setenv bootlimit 0

reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verify it works
&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;# O5 = Operation State = OLT accepted the stick as a valid ONT&lt;/span&gt;
diag gpon get onu-state
&lt;span class="c"&gt;# ONU state: Operation State(O5)&lt;/span&gt;

&lt;span class="c"&gt;# Correct OLT vendor&lt;/span&gt;
omcicli mib get 131
&lt;span class="c"&gt;# OltVendorId: 0x48575443  ← HWTC (Huawei/Entel Chile) ✓&lt;/span&gt;

&lt;span class="c"&gt;# VLANs provisioned by Entel's OLT&lt;/span&gt;
omcicli mib get 84
&lt;span class="c"&gt;# VLAN 3610 → Internet | 3620 → TV | 3630 → VoIP | 3680 → TR-069&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the stick at O5: configure the Ubiquiti SFP+ port with VLAN 3610 and PPPoE with Entel credentials (&lt;code&gt;20011279169002&lt;/code&gt; / &lt;code&gt;D4310T&lt;/code&gt; — tested on 2026-04-23, subject to change).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎥 &lt;strong&gt;&lt;a href="https://drive.google.com/file/d/1jLBNIA8xwyT7rVpS8N7n40CP2AoTfu8s/view?usp=sharing" rel="noopener noreferrer"&gt;The official Entel modem farewell ceremony&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Trade-offs: when NOT to do this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No UART/U-Boot experience:&lt;/strong&gt; The process requires bootloader commands. A wrong address in &lt;code&gt;sf write&lt;/code&gt; can brick the stick (recoverable via TFTP if U-Boot still responds).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using a media converter (TP-Link MC220L):&lt;/strong&gt; The &lt;code&gt;europa_drv&lt;/code&gt; kernel panics with that hardware as an intermediary. The UF-Instant must go &lt;strong&gt;directly&lt;/strong&gt; into the router's SFP+ slot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ISP uses authentication beyond PLOAM serial:&lt;/strong&gt; Some ISPs also validate PLOAM password, OMCI details, or run clone detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using Entel's TV or VoIP:&lt;/strong&gt; Those run on separate VLANs (3620 and 3630). You'll need to configure them on the router too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The failed attempts, kernel panic details, and the MC220L diagnosis are documented in full on ☕ &lt;a href="https://buymeacoffee.com/alpha018/how-i-got-rid-my-isp-modem-hacking-sfp-gpon-transceiver" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Glossary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gigabit Passive Optical Network. Fiber delivering up to 2.5 Gbps downstream shared among multiple users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ONT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optical Network Terminal. The device at your location converting optical signal to ethernet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OLT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optical Line Termination. ISP equipment managing all ONTs in an area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SFP / SFP+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small Form-factor Pluggable. Standard connectivity module. SFP+ supports up to 10 Gbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PLOAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Physical Layer Operations, Administration and Maintenance. Authentication protocol between ONT and OLT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OMCI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ONT Management and Control Interface. Channel through which the OLT remotely manages the ONT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PPPoE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Point-to-Point Protocol over Ethernet. Authentication protocol ISPs use for internet connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MTD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Memory Technology Device. Linux interface for embedded flash storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;U-Boot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Universal bootloader for embedded systems. Lets you flash partitions if the OS won't start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UART&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Universal Asynchronous Receiver-Transmitter. Serial port for console access before Linux loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TFTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Trivial File Transfer Protocol. Minimal protocol for transferring firmware to embedded devices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spoofing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Impersonating another device's identity to the OLT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Resources and credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/stich86/UF-Instant-Mod" rel="noopener noreferrer"&gt;stich86/UF-Instant-Mod&lt;/a&gt; — Modified rootfs for UF-Instant&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Anime4000/RTL960x" rel="noopener noreferrer"&gt;Anime4000/RTL960x&lt;/a&gt; — Firmware and RTL960x documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hack-gpon.org/" rel="noopener noreferrer"&gt;hack-gpon.org&lt;/a&gt; — Community GPON hack reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://false-balaur-bca.notion.site/MA5671A-Configuration-Guide-HEX-PLOAM-2eee31cf27198055a79afb46d7b17b2d" rel="noopener noreferrer"&gt;MA5671A Config Guide (PLOAM)&lt;/a&gt; — PLOAM configuration guide&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you've done something similar with a different ISP or OLT vendor, let me know in the comments. I'd like to know which parameters change between carriers.&lt;/p&gt;




&lt;h3&gt;
  
  
  💌 Special Acknowledgments
&lt;/h3&gt;

&lt;p&gt;To a girl who, without knowing it, pushed me to keep creating and coding in my free time. To the one who was once my spark: for "YLP", with gratitude... and with scars that also teach.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;For those who also refused "it can't be done" as a final answer: this post exists because I was told I wouldn't get it working. And because fiber now reaches my router without passing through any hardware I didn't choose. Nobody decides that for me.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;🖕 Fuck you, Entel. Your bargain-bin devs don't get to decide what I plug into my own network. And you, Movistar — don't get comfortable. I know you run ZTE and you're next on the list.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>homelab</category>
      <category>linux</category>
      <category>sfp</category>
    </item>
    <item>
      <title>☠️ Cómo conectar tu router directo a la fibra de Entel Chile sin su módem (SFP GPON Hack)</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Fri, 24 Apr 2026 05:54:04 +0000</pubDate>
      <link>https://dev.to/alpha018/como-conectar-tu-router-directo-a-la-fibra-de-entel-chile-sin-su-modem-sfp-gpon-hack-19nd</link>
      <guid>https://dev.to/alpha018/como-conectar-tu-router-directo-a-la-fibra-de-entel-chile-sin-su-modem-sfp-gpon-hack-19nd</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/alpha018/how-to-connect-your-router-directly-to-fiber-without-your-isps-modem-sfp-gpon-hack-15b2"&gt;🇺🇸 Read this post in English&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Entel Chile me dijo que el bridge mode no existía para clientes empresa. Una semana después, mi router Ubiquiti estaba conectado directo a la fibra sin ningún equipo de Entel de por medio.&lt;/p&gt;

&lt;p&gt;Este post explica cómo: un transceptor SFP GPON (Ubiquiti UF-Instant), acceso por UART, flash directo via U-Boot y configuración de spoofing GPON para que la OLT de Entel acepte el stick como si fuera el ONT Huawei original.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Versión resumida.&lt;/strong&gt; Este post cubre el proceso sin los intentos fallidos ni los callejones sin salida. La versión extendida — con el kernel panic del MC220L, los tres intentos de firmware y la recuperación por TFTP — está en &lt;a href="https://buymeacoffee.com/alpha018/cmo-deshice-del-mdem-de-entel-chile-hackeando-un-transceptor-sfp-gpon" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Disclaimer legal:&lt;/strong&gt; Este contenido es exclusivamente educativo. Modificar firmware de dispositivos de telecomunicaciones puede vulnerar los términos de tu contrato con el ISP y potencialmente regulaciones locales. Consulta la normativa vigente en tu país antes de replicar este procedimiento.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  El problema: vendor lock-in de ISP
&lt;/h2&gt;

&lt;p&gt;Los ISP instalan su propio ONT y se niegan a configurarlo en bridge mode, forzándote a usar su router para PPPoE. Si quieres usar el tuyo, las opciones son doble NAT o bypassar completamente el hardware del ISP.&lt;/p&gt;

&lt;p&gt;Quería dual WAN en mi Ubiquiti UniFi Gateway Fiber con IP estática de Entel. Sin doble NAT. Sin el módem de Entel.&lt;/p&gt;

&lt;h2&gt;
  
  
  El hardware
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Componente&lt;/th&gt;
&lt;th&gt;Descripción&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transceptor&lt;/td&gt;
&lt;td&gt;Ubiquiti UFiber UF-Instant (RTL9601CI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;td&gt;Ubiquiti UniFi Gateway Fiber (Dual WAN, slot SFP+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ISP&lt;/td&gt;
&lt;td&gt;Entel Chile (OLT Huawei)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONT original&lt;/td&gt;
&lt;td&gt;Huawei OptiXstar (clonado para spoofing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Acceso de consola&lt;/td&gt;
&lt;td&gt;Adaptador UART TTL (3.3V obligatorio)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Servidor TFTP&lt;/td&gt;
&lt;td&gt;Mac con tftp-now o tftp-hpa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preparación del rootfs&lt;/td&gt;
&lt;td&gt;Docker (necesario en macOS)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1byf16a7ihoeoss8i5k.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1byf16a7ihoeoss8i5k.jpeg" alt="UF-Instant junto al adaptador UART TTL — cables soldados a los pads de la PCB" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6cx02igafiw98rl4peg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6cx02igafiw98rl4peg.jpeg" alt="Setup del experimento — transceptor modificado conectado al switch y hub USB-C" width="720" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnracnn29o2nxcoc7qeql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnracnn29o2nxcoc7qeql.png" alt="Referencia de pinout del UF-Instant (Rx, Tx, GND, VCC)" width="800" height="191"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Fuente: &lt;a href="https://github.com/stich86/UF-Instant-Mod" rel="noopener noreferrer"&gt;stich86/UF-Instant-Mod&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Acceso por UART: el método principal
&lt;/h2&gt;

&lt;p&gt;Todo el proceso de flash se ejecuta desde aquí. El UF-Instant expone pads UART en la PCB (TX, RX, GND). Adaptador 3.3V, configuración &lt;code&gt;115200 8N1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
screen /dev/tty.usbserial-XXXXX 115200

&lt;span class="c"&gt;# Linux&lt;/span&gt;
screen /dev/ttyUSB0 115200

&lt;span class="c"&gt;# Con minicom&lt;/span&gt;
minicom &lt;span class="nt"&gt;-D&lt;/span&gt; /dev/ttyUSB0 &lt;span class="nt"&gt;-b&lt;/span&gt; 115200 &lt;span class="nt"&gt;-8&lt;/span&gt; &lt;span class="nt"&gt;--noinit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Al encender el stick con UART conectado, tienes el output completo de U-Boot. Presiona cualquier tecla en los primeros segundos para interrumpir el boot y quedar en el prompt &lt;code&gt;9601C#&lt;/code&gt;, desde donde puedes flashear particiones directamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapa de particiones MTD
&lt;/h2&gt;

&lt;p&gt;El UF-Instant usa un sistema de arranque dual. Las particiones relevantes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mtd4  → kernel0 / k0 (3MB)   — Kernel imagen 0 (Ubiquiti original — respaldo)
mtd5  → rootfs0 / r0 (6MB)   — Rootfs imagen 0 (firmware Ubiquiti original)
mtd6  → kernel1 / k1 (3MB)   — Kernel imagen 1 — aquí va el clon
mtd7  → rootfs1 / r1 (4.5MB) — Rootfs imagen 1 — aquí va el MOD stich86
mtd8  → europa.data          — Calibración del láser ⚠️ JAMÁS TOCAR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Control de arranque:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Imagen activa&lt;/span&gt;
nv getenv sw_active sw_commit

&lt;span class="c"&gt;# Cambiar a imagen 1&lt;/span&gt;
setenv sw_active 1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; setenv sw_commit 1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; saveenv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; boot

&lt;span class="c"&gt;# Volver a imagen 0 (firmware original Ubiquiti)&lt;/span&gt;
setenv sw_active 0 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; setenv sw_commit 0 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; saveenv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; boot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  El proceso: directo al grano
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;En el host&lt;/strong&gt; (antes de conectar UART): descargar el rootfs MOD y el kernel de Ubiquiti v4.4.2 directamente del repo &lt;a href="https://github.com/stich86/UF-Instant-Mod" rel="noopener noreferrer"&gt;stich86/UF-Instant-Mod&lt;/a&gt; y ponerlos en el directorio del servidor TFTP. El binario squashfs de stich86 se flashea tal como viene — no requiere reprocesado.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;¿Cuándo se necesita Docker?&lt;/strong&gt; Solo si modificas el rootfs (extraer, cambiar archivos y reempaquetar). En macOS, &lt;code&gt;mksquashfs&lt;/code&gt; no puede recrear device nodes sin parámetros explícitos. Para el flujo estándar con el binario de stich86, no hace falta.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Via UART, en U-Boot (&lt;code&gt;9601C#&lt;/code&gt;):&lt;/strong&gt; inicializar flash, clonar el kernel de imagen 0 a imagen 1, y flashear el rootfs MOD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sf probe 0

&lt;span class="c"&gt;# Clonar kernel Ubiquiti v4.4.2 de imagen 0 → imagen 1&lt;/span&gt;
sf &lt;span class="nb"&gt;read  &lt;/span&gt;0x81000000 0x50000  0x300000   &lt;span class="c"&gt;# leer kernel0 a RAM&lt;/span&gt;
sf erase 0x4e0000 0x300000              &lt;span class="c"&gt;# borrar kernel1 (mtd6)&lt;/span&gt;
sf write 0x81000000 0x4e0000 0x300000  &lt;span class="c"&gt;# escribir en mtd6&lt;/span&gt;

&lt;span class="c"&gt;# Flashear rootfs MOD (stich86) via TFTP&lt;/span&gt;
tftpboot 0x81000000 &lt;span class="o"&gt;[&lt;/span&gt;IP_HOST]:[nombre_rootfs_stich86]
sf erase 0x7e0000 0x480000              &lt;span class="c"&gt;# borrar rootfs1 (mtd7)&lt;/span&gt;
sf write 0x81000000 0x7e0000 &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filesize&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Activar imagen 1 y arrancar&lt;/span&gt;
setenv sw_active 1
setenv sw_commit 1
saveenv
boot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Via UART, desde Linux&lt;/strong&gt; (tras arrancar imagen 1, credenciales &lt;code&gt;admin&lt;/code&gt;/&lt;code&gt;admin&lt;/code&gt; — algunos builds usan &lt;code&gt;ubnt&lt;/code&gt;/&lt;code&gt;ubnt&lt;/code&gt;): configurar la identidad GPON con los datos del ONT Huawei original y neutralizar el bootlimit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Identidad GPON — datos del ONT Huawei de tu instalación&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;GPON_SN HWTC[XXXXXXXX]          &lt;span class="c"&gt;# SN del ONT: HWTC + 8 hex chars&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;PON_VENDOR_ID HWTC
flash &lt;span class="nb"&gt;set &lt;/span&gt;OMCI_OLT_MODE 1
flash &lt;span class="nb"&gt;set &lt;/span&gt;OMCI_FAKE_OK 1
flash &lt;span class="nb"&gt;set &lt;/span&gt;OMCI_SW_VER1 &lt;span class="o"&gt;[&lt;/span&gt;VERSION_SW_ONT]   &lt;span class="c"&gt;# Versión software del ONT&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;OMCI_SW_VER2 &lt;span class="o"&gt;[&lt;/span&gt;VERSION_SW_ONT]
flash &lt;span class="nb"&gt;set &lt;/span&gt;HW_HWVER &lt;span class="o"&gt;[&lt;/span&gt;VERSION_HW_ONT]       &lt;span class="c"&gt;# Versión hardware del ONT&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;GPON_ONU_MODEL &lt;span class="o"&gt;[&lt;/span&gt;MODELO_ONT]     &lt;span class="c"&gt;# Modelo del ONT&lt;/span&gt;
flash &lt;span class="nb"&gt;set &lt;/span&gt;ELAN_MAC_ADDR &lt;span class="o"&gt;[&lt;/span&gt;MAC_SIN_PUNTOS]  &lt;span class="c"&gt;# MAC del ONT sin dos puntos&lt;/span&gt;

&lt;span class="c"&gt;# Desactivar factory reset automático (bootlimit=10 por defecto)&lt;/span&gt;
nv setenv bootcount 0
nv setenv bootlimit 0

reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verificar que funciona
&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;# O5 = Operation State = la OLT aceptó el stick como ONT válido&lt;/span&gt;
diag gpon get onu-state
&lt;span class="c"&gt;# ONU state: Operation State(O5)&lt;/span&gt;

&lt;span class="c"&gt;# Vendor OLT correcto&lt;/span&gt;
omcicli mib get 131
&lt;span class="c"&gt;# OltVendorId: 0x48575443  ← HWTC (Huawei/Entel Chile) ✓&lt;/span&gt;

&lt;span class="c"&gt;# VLANs provisionadas por la OLT de Entel&lt;/span&gt;
omcicli mib get 84
&lt;span class="c"&gt;# VLAN 3610 → Internet | 3620 → TV | 3630 → VoIP | 3680 → TR-069&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con el stick en O5: en el router Ubiquiti configurar el puerto SFP+ con VLAN 3610 y PPPoE con las credenciales de Entel (&lt;code&gt;20011279169002&lt;/code&gt; / &lt;code&gt;D4310T&lt;/code&gt; — probadas el 2026-04-23, pueden cambiar).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎥 &lt;strong&gt;&lt;a href="https://drive.google.com/file/d/1jLBNIA8xwyT7rVpS8N7n40CP2AoTfu8s/view?usp=sharing" rel="noopener noreferrer"&gt;La despedida oficial del módem de Entel&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Trade-offs: cuándo NO hacer esto
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sin experiencia con UART/U-Boot:&lt;/strong&gt; El proceso requiere comandos en el bootloader. Un error de dirección en &lt;code&gt;sf write&lt;/code&gt; puede brick el stick (recuperable via TFTP si U-Boot sigue respondiendo).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Con un media converter (TP-Link MC220L):&lt;/strong&gt; El driver &lt;code&gt;europa_drv&lt;/code&gt; hace kernel panic con ese hardware como intermediario. El UF-Instant debe ir &lt;strong&gt;directamente&lt;/strong&gt; al slot SFP+ del router.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Si tu ISP valida más que el serial GPON:&lt;/strong&gt; Algunos ISPs verifican también el PLOAM password, detalles OMCI, o tienen detección de clones activa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Si usas TV o VoIP de Entel:&lt;/strong&gt; Funcionan en VLANs separadas (3620 y 3630). Deberás configurarlas también en el router.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Los intentos fallidos, los errores de kernel panic y el diagnóstico del MC220L están documentados en detalle en ☕ &lt;a href="https://buymeacoffee.com/alpha018/cmo-deshice-del-mdem-de-entel-chile-hackeando-un-transceptor-sfp-gpon" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Glosario
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Término&lt;/th&gt;
&lt;th&gt;Qué es&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gigabit Passive Optical Network. Fibra óptica que entrega hasta 2.5 Gbps downstream compartidos entre múltiples usuarios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ONT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optical Network Terminal. El dispositivo en tu casa que convierte la señal óptica a ethernet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OLT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optical Line Termination. El equipo del ISP que gestiona todos los ONT del área&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SFP / SFP+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small Form-factor Pluggable. Módulo estándar de conectividad. SFP+ soporta hasta 10 Gbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PLOAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Physical Layer Operations, Administration and Maintenance. El protocolo de autenticación entre ONT y OLT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OMCI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ONT Management and Control Interface. Canal por donde la OLT gestiona remotamente el ONT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PPPoE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Point-to-Point Protocol over Ethernet. Protocolo de autenticación de los ISPs para las conexiones de internet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MTD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Memory Technology Device. Interfaz de Linux para la flash interna del dispositivo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;U-Boot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bootloader universal para sistemas embebidos. Permite flashear particiones si el SO no arranca&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UART&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Universal Asynchronous Receiver-Transmitter. Puerto serie para acceder a la consola antes de que Linux cargue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TFTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Trivial File Transfer Protocol. Protocolo minimalista para transferir firmware a dispositivos embebidos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spoofing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Suplantar la identidad de otro dispositivo ante la OLT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  💌 &lt;strong&gt;Agradecimientos Especiales&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A una chica que, sin saberlo, me empujó a seguir creando y programando en mi tiempo libre. A quien alguna vez fue mi chispa: para “YLP”, con gratitud… y con cicatrices que también enseñan.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recursos y créditos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/stich86/UF-Instant-Mod" rel="noopener noreferrer"&gt;stich86/UF-Instant-Mod&lt;/a&gt; — MOD rootfs para UF-Instant&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Anime4000/RTL960x" rel="noopener noreferrer"&gt;Anime4000/RTL960x&lt;/a&gt; — Firmware y documentación RTL960x&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hack-gpon.org/" rel="noopener noreferrer"&gt;hack-gpon.org&lt;/a&gt; — Referencia comunitaria de hacks GPON&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://false-balaur-bca.notion.site/MA5671A-Configuration-Guide-HEX-PLOAM-2eee31cf27198055a79afb46d7b17b2d" rel="noopener noreferrer"&gt;MA5671A Config Guide (PLOAM)&lt;/a&gt; — Guía de configuración PLOAM&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Si tienes preguntas o tu ISP usa una OLT diferente, déjalas en los comentarios. Y si ya lo has hecho con otro proveedor, cuéntame qué parámetros cambiaron.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Para quien tampoco aceptó el "no se puede" como respuesta final: este post existe porque me dijeron que no lo lograría. Y porque la fibra llega al router sin pasar por ningún equipo que no sea el mío. Nadie decide eso por mí.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;🖕 Fuck you, Entel. Tus devs de pacotilla no me dicen qué conectar en mi propia red. Y tú, Movistar — estate quieta. Sé que usas ZTE y lo tengo en el radar.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>homelab</category>
      <category>linux</category>
      <category>sfp</category>
    </item>
    <item>
      <title>🔬 NestJS vs Go: Performance Benchmark (Do you really need to migrate?)</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Tue, 10 Mar 2026 02:00:34 +0000</pubDate>
      <link>https://dev.to/alpha018/nestjs-vs-go-performance-benchmark-do-you-really-need-to-migrate-26eb</link>
      <guid>https://dev.to/alpha018/nestjs-vs-go-performance-benchmark-do-you-really-need-to-migrate-26eb</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/alpha018/nestjs-vs-go-benchmark-de-rendimiento-realmente-necesitas-migrar-4cd0"&gt;🇪🇸 Lee este post en Español&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hello everyone. I recently conducted an exhaustive benchmark to answer a classic question in backend teams: &lt;strong&gt;does it make sense to migrate to Go, or is NestJS enough?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is no magic answer, but data helps decide. Here is a summary of the most important findings.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ The Scenario
&lt;/h2&gt;

&lt;p&gt;I compared two typical architectures running in Docker on the same machine (M4 Pro):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;NestJS (Node 22) + Prisma v6&lt;/strong&gt; (Productivity approach)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Go (1.24) + GORM&lt;/strong&gt; (Performance approach)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both connected to &lt;strong&gt;PostgreSQL 15&lt;/strong&gt; and subjected to stress tests (gRPC and HTTP) simulating a real environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Methodology: What did we measure?
&lt;/h2&gt;

&lt;p&gt;To truly understand the differences, I divided the tests into two key scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small Data (1x1 Insert):&lt;/strong&gt;&lt;br&gt;
Simulates typical transactional traffic of a REST/gRPC API. We receive an object, validate it, and save it. Here we measure the framework's base latency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large Data (Batch Insert of 1000 elements):&lt;/strong&gt;&lt;br&gt;
Simulates bulk load processes, ETLs, or event ingestion. We send arrays of 1000 objects that must be deserialized, validated, and saved in a single transaction. This is where &lt;strong&gt;CPU and Garbage Collector&lt;/strong&gt; suffer the most.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📊 The Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Small Data: The Surprise
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;low load or unit insert&lt;/strong&gt; scenarios, the difference was minimal. Go obtained a latency improvement of just &lt;strong&gt;~10%&lt;/strong&gt; over NestJS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; For standard CRUD operations, NestJS is incredibly efficient, and the performance difference rarely justifies losing TypeScript's productivity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Large Data: The Breaking Point
&lt;/h3&gt;

&lt;p&gt;The story changes drastically when processing massive batches (arrays of 1000 elements) under high concurrency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario: 100 RPS constant (Bulk Inserts)&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;Service&lt;/th&gt;
&lt;th&gt;Real Throughput&lt;/th&gt;
&lt;th&gt;P95 Latency&lt;/th&gt;
&lt;th&gt;Success Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go gRPC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85 RPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;71.88 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NestJS gRPC&lt;/td&gt;
&lt;td&gt;78 RPS&lt;/td&gt;
&lt;td&gt;98.44 ms&lt;/td&gt;
&lt;td&gt;~85%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What happened here?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Serialization:&lt;/strong&gt; Node.js (V8) has a higher cost when serializing/deserializing large volumes of JSON compared to Go Structs and Protobuf.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Connection Pool:&lt;/strong&gt; Under extreme pressure, the Prisma connection pool started to saturate (&lt;code&gt;P2024&lt;/code&gt; errors), causing &lt;strong&gt;NestJS to lose ~15% of requests&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Stability:&lt;/strong&gt; Go maintained 100% success and more predictable latencies, thanks to its goroutine management and a more efficient GC for this type of load.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚖️ Conclusion: When to use which?
&lt;/h2&gt;

&lt;p&gt;Based on the data, here is my pragmatic recommendation:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Stay with NestJS if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  You are building an MVP or validating a product.&lt;/li&gt;
&lt;li&gt;  Your team already knows TypeScript and you value development speed.&lt;/li&gt;
&lt;li&gt;  The load is moderate (&amp;lt; 300 RPS) and standard CRUD operations (Small Data).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;BFF (Backend for Frontend):&lt;/strong&gt; If you only need to orchestrate calls to a few services and the serialization load is low.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚀 Migrate to Go if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Complex BFF:&lt;/strong&gt; If your BFF needs to call many microservices, add heavy logic, serialize large volumes of data, and perform massive fan-out, Go will handle concurrency much better.&lt;/li&gt;
&lt;li&gt;  Your system constantly performs massive &lt;strong&gt;Bulk Operations / ETL&lt;/strong&gt; (Large Data).&lt;/li&gt;
&lt;li&gt;  You need to handle thousands of concurrent connections (e.g., Gateways, massive Websockets).&lt;/li&gt;
&lt;li&gt;  You have very strict latency SLAs where Node's Garbage Collector spikes are unacceptable.&lt;/li&gt;
&lt;li&gt;  Infrastructure is expensive and you need to squeeze every CPU cycle.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📖 Read the full analysis
&lt;/h2&gt;

&lt;p&gt;This post is a summary. The detailed analysis (with all tables, charts, hardware configuration, and error analysis) is &lt;strong&gt;public and free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://buymeacoffee.com/alpha018/nestjs-vs-go-what-data-says-use" rel="noopener noreferrer"&gt;Read the full article on Buy Me a Coffee&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;💻 &lt;strong&gt;&lt;a href="https://github.com/Alpha018/grpc-http-performance-lab" rel="noopener noreferrer"&gt;View code on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;💌 &lt;strong&gt;Special Acknowledgments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To a girl who, without knowing it, pushed me to keep creating and programming in my free time. To the one who was once my spark: for "YLP", with gratitude... and with scars that also teach.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you find this analysis useful, any feedback is welcome!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>backend</category>
      <category>nestjs</category>
      <category>performance</category>
    </item>
    <item>
      <title>🔬 NestJS vs Go: Benchmark de Rendimiento (¿Realmente necesitas migrar?)</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Mon, 09 Mar 2026 00:17:36 +0000</pubDate>
      <link>https://dev.to/alpha018/nestjs-vs-go-benchmark-de-rendimiento-realmente-necesitas-migrar-4cd0</link>
      <guid>https://dev.to/alpha018/nestjs-vs-go-benchmark-de-rendimiento-realmente-necesitas-migrar-4cd0</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/alpha018/nestjs-vs-go-performance-benchmark-do-you-really-need-to-migrate-26eb"&gt;🇺🇸 Read this post in English&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hola a todos. Recientemente realicé un benchmark exhaustivo para responder una pregunta clásica en equipos de backend: &lt;strong&gt;¿tiene sentido migrar a Go, o alcanza con NestJS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No hay respuesta mágica, pero los datos ayudan a decidir. Aquí les dejo un resumen de los hallazgos más importantes.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ El Escenario
&lt;/h2&gt;

&lt;p&gt;Comparé dos arquitecturas típicas corriendo en Docker sobre la misma máquina (M4 Pro):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;NestJS (Node 22) + Prisma v6&lt;/strong&gt; (Enfoque productividad)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Go (1.24) + GORM&lt;/strong&gt; (Enfoque rendimiento)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ambos conectados a &lt;strong&gt;PostgreSQL 15&lt;/strong&gt; y sometidos a pruebas de estrés (gRPC y HTTP) simulando un entorno real.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Metodología: ¿Qué medimos?
&lt;/h2&gt;

&lt;p&gt;Para entender realmente las diferencias, dividí las pruebas en dos escenarios clave:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small Data (1x1 Insert):&lt;/strong&gt;&lt;br&gt;
Simula el tráfico transaccional típico de una API REST/gRPC. Recibimos un objeto, lo validamos y lo guardamos. Aquí medimos la latencia base del framework.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large Data (Batch Insert de 1000 elementos):&lt;/strong&gt;&lt;br&gt;
Simula procesos de carga masiva, ETLs o ingestión de eventos. Enviamos arrays de 1000 objetos que deben ser deserializados, validados y guardados en una sola transacción. Aquí es donde &lt;strong&gt;la CPU y el Garbage Collector&lt;/strong&gt; sufren más.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📊 Los Resultados
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Small Data: La sorpresa
&lt;/h3&gt;

&lt;p&gt;En escenarios de &lt;strong&gt;baja carga o inserciones unitarias&lt;/strong&gt;, la diferencia fue mínima. Go obtuvo una mejora de latencia de apenas un &lt;strong&gt;~10%&lt;/strong&gt; sobre NestJS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Conclusión:&lt;/strong&gt; Para operaciones CRUD estándar, NestJS es increíblemente eficiente y la diferencia de rendimiento rara vez justifica perder la productividad de TypeScript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Large Data: El punto de quiebre
&lt;/h3&gt;

&lt;p&gt;La historia cambia drásticamente cuando procesamos lotes masivos (arrays de 1000 elementos) bajo alta concurrencia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escenario: 100 RPS constantes (Bulk Inserts)&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;Servicio&lt;/th&gt;
&lt;th&gt;Throughput Real&lt;/th&gt;
&lt;th&gt;P95 Latency&lt;/th&gt;
&lt;th&gt;Success Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go gRPC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85 RPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;71.88 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NestJS gRPC&lt;/td&gt;
&lt;td&gt;78 RPS&lt;/td&gt;
&lt;td&gt;98.44 ms&lt;/td&gt;
&lt;td&gt;~85%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;¿Qué pasó aquí?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Serialización:&lt;/strong&gt; Node.js (V8) tiene un costo mayor al serializar/deserializar grandes volúmenes de JSON comparado con los Structs de Go y Protobuf.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Connection Pool:&lt;/strong&gt; Bajo presión extrema, el pool de conexiones de Prisma comenzó a saturarse (errores &lt;code&gt;P2024&lt;/code&gt;), provocando que &lt;strong&gt;NestJS perdiera el ~15% de los requests&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Estabilidad:&lt;/strong&gt; Go mantuvo el 100% de éxito y latencias más predecibles, gracias a su manejo de goroutines y un GC más eficiente para este tipo de carga.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚖️ Conclusión: ¿Cuándo usar cuál?
&lt;/h2&gt;

&lt;p&gt;Basado en los datos, aquí está mi recomendación pragmática:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Quédate con NestJS si:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Estás construyendo un MVP o validando producto.&lt;/li&gt;
&lt;li&gt;  Tu equipo ya conoce TypeScript y valoras la velocidad de desarrollo.&lt;/li&gt;
&lt;li&gt;  La carga es moderada (&amp;lt; 300 RPS) y operaciones CRUD estándar (Small Data).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;BFF (Backend for Frontend):&lt;/strong&gt; Si solo necesitas orquestar llamadas a pocos servicios y la carga de serialización es baja.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚀 Migra a Go si:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;BFF Complejo:&lt;/strong&gt; Si tu BFF necesita llamar a muchos microservicios, agregar lógica pesada, serializar grandes volúmenes de datos y hacer fan-out masivo, Go manejará la concurrencia mucho mejor.&lt;/li&gt;
&lt;li&gt;  Tu sistema realiza &lt;strong&gt;Bulk Operations / ETL&lt;/strong&gt; masivos constantemente (Large Data).&lt;/li&gt;
&lt;li&gt;  Necesitas manejar miles de conexiones concurrentes (ej. Gateways, Websockets masivos).&lt;/li&gt;
&lt;li&gt;  Tienes SLAs de latencia muy estrictos donde los picos del Garbage Collector de Node son inaceptables.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📖 Lee el análisis completo
&lt;/h2&gt;

&lt;p&gt;Este post es un resumen. El análisis detallado (con todas las tablas, gráficos, configuración de hardware y análisis de errores) es &lt;strong&gt;público y gratuito&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://buymeacoffee.com/alpha018/nestjs-vs-go-lo-que-los-datos-dicen-sobre-cundo-usar-cada-uno" rel="noopener noreferrer"&gt;Leer el artículo completo en Buy Me a Coffee&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;💻 &lt;strong&gt;&lt;a href="https://github.com/Alpha018/grpc-http-performance-lab" rel="noopener noreferrer"&gt;Ver el código en GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💌 &lt;strong&gt;Agradecimientos Especiales&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A una chica que, sin saberlo, me empujó a seguir creando y programando en mi tiempo libre. A quien alguna vez fue mi chispa: para “YLP”, con gratitud… y con cicatrices que también enseñan.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Si te sirve este tipo de análisis, ¡cualquier feedback es bienvenido!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>backend</category>
      <category>nestjs</category>
      <category>performance</category>
    </item>
    <item>
      <title>🐳 Minecraft + Docker: My personal config to stop suffering with Java</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Thu, 05 Feb 2026 04:19:41 +0000</pubDate>
      <link>https://dev.to/alpha018/minecraft-docker-my-personal-config-to-stop-suffering-with-java-35a7</link>
      <guid>https://dev.to/alpha018/minecraft-docker-my-personal-config-to-stop-suffering-with-java-35a7</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/alpha018/minecraft-docker-mi-configuracion-personal-para-no-sufrir-con-java-27k"&gt;🇪🇸 Lee este post en Español&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm no Java guru, and I'll be honest: managing Minecraft servers has always felt like a weird puzzle to me. Even though Minecraft might look like a "random" game about placing blocks, the technical management behind a server (threads, memory, garbage collection) is a fascinating optimization challenge.&lt;/p&gt;

&lt;p&gt;Back in the day, I tried creating plugins (with... "interesting" results), but I learned the hard way that the real battle is almost always the configuration.&lt;/p&gt;

&lt;p&gt;Has this happened to you? You want to play Survival with friends, you search for "best paper 1.21 config", and you end up with 20 open tabs and three wikis that contradict each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Initiative: Enough Chaos
&lt;/h2&gt;

&lt;p&gt;Tired of copying and pasting the same configuration lines over and over again, I decided to build &lt;strong&gt;&lt;a href="https://github.com/Alpha018/paper-config-optimized" rel="noopener noreferrer"&gt;Paper Config Optimized&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I didn't want to make "just another guide". I wanted a place to truly centralize the community's best practices into ready-to-use files. In fact, I've become such a fan of this "infrastructure as code" approach that I recently made my first contribution to the official Docker image repository we use (PR #3076 in &lt;code&gt;itzg/docker-minecraft-server&lt;/code&gt;) and plan to keep contributing to improve the ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  "The Recipe" (Configuration)
&lt;/h2&gt;

&lt;p&gt;Here is the configuration I always use. It's basically my "copy, paste, and forget" template.&lt;/p&gt;

&lt;p&gt;The important thing here is that we are telling Docker two key things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"Use Aikar's Flags"&lt;/strong&gt;: So Java doesn't get picky with memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Download configs from the repo"&lt;/strong&gt;: So the game runs smoothly without having to touch anything.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;itzg/minecraft-server&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paper-lite&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;25565:25565"&lt;/span&gt;

    &lt;span class="c1"&gt;# Interactive console (never hurts to see what's happening)&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Mojang bureaucracy and software choice (Paper flies)&lt;/span&gt;
      &lt;span class="na"&gt;EULA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TRUE"&lt;/span&gt;
      &lt;span class="na"&gt;TYPE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PAPER"&lt;/span&gt;

      &lt;span class="c1"&gt;# Always the latest. Living on the edge.&lt;/span&gt;
      &lt;span class="na"&gt;VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LATEST"&lt;/span&gt;

      &lt;span class="c1"&gt;# We activate Aikar's flags.&lt;/span&gt;
      &lt;span class="c1"&gt;# Trust me, you don't want to manage the Garbage Collector manually.&lt;/span&gt;
      &lt;span class="na"&gt;USE_AIKAR_FLAGS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;

      &lt;span class="c1"&gt;# Timezone (change to yours so logs make sense)&lt;/span&gt;
      &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/Santiago"&lt;/span&gt;

      &lt;span class="c1"&gt;# ✨ THE MAGIC:&lt;/span&gt;
      &lt;span class="c1"&gt;# Automatically injects optimized configs from the repo at startup.&lt;/span&gt;
      &lt;span class="na"&gt;PAPER_CONFIG_REPO&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://raw.githubusercontent.com/Alpha018/paper-config-optimized/main"&lt;/span&gt;

    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./minecraft-data:/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How to run this?
&lt;/h3&gt;

&lt;p&gt;It's as simple as putting that into a &lt;code&gt;docker-compose.yml&lt;/code&gt; file, opening a terminal in the folder, and throwing in a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚖️ Balance: Pros and Cons
&lt;/h2&gt;

&lt;p&gt;As with everything in engineering, there are no silver bullets. This configuration is my favorite, but we have to be transparent:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ The Good (Pros)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Go from "zero" to "optimized server" in 2 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance&lt;/strong&gt;: By using &lt;code&gt;PAPER_CONFIG_REPO&lt;/code&gt;, if performance improvements come out tomorrow, you just have to restart the container to apply them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stability&lt;/strong&gt;: Using &lt;em&gt;Aikar's Flags&lt;/em&gt; eliminates 90% of lag spikes caused by Java.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ The Bad (Cons)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Black Box&lt;/strong&gt;: If you like manually adjusting every millimeter of &lt;code&gt;view-distance&lt;/code&gt;, you might feel like you're losing some control (although you can always overwrite the files).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency&lt;/strong&gt;: You depend on my repository (or your own fork) being online to download the configs at startup.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 For Advanced Users
&lt;/h2&gt;

&lt;p&gt;This configuration is ideal for what I call "Lite Technical Survival". But if you are planning to set up a massive public community (50+ players) or want to understand why 12GB of RAM is my magic number for large servers, I wrote a much deeper technical guide.&lt;/p&gt;

&lt;p&gt;☕ &lt;strong&gt;&lt;a href="https://buymeacoffee.com/alpha018/ultra-optimized-papermc-docker-a-technical-deep-dive" rel="noopener noreferrer"&gt;Check out the Advanced Technical Guide on Buy Me a Coffee&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  💌 Special Acknowledgments
&lt;/h3&gt;

&lt;p&gt;To a girl who, without knowing it, pushed me to keep creating and coding in my free time. To the one who was once my spark: for "YLP", with gratitude... and with scars that also teach.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>minecraft</category>
      <category>performance</category>
      <category>java</category>
    </item>
    <item>
      <title>🐳 Minecraft + Docker: Mi configuración personal para no sufrir con Java</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Thu, 05 Feb 2026 04:18:11 +0000</pubDate>
      <link>https://dev.to/alpha018/minecraft-docker-mi-configuracion-personal-para-no-sufrir-con-java-27k</link>
      <guid>https://dev.to/alpha018/minecraft-docker-mi-configuracion-personal-para-no-sufrir-con-java-27k</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/alpha018/minecraft-docker-my-personal-config-to-stop-suffering-with-java-35a7"&gt;🇺🇸 Read this post in English&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No soy ningún gurú de Java, y seré honesto: gestionar servidores de Minecraft siempre me pareció un rompecabezas extraño. A pesar de que Minecraft pueda parecer un juego "random" de poner cubitos, la gestión técnica detrás de un servidor (hilos, memoria, recolección de basura) es un reto de optimización fascinante.&lt;/p&gt;

&lt;p&gt;En su momento intenté crear plugins (con resultados... "interesantes"), pero aprendí a la mala que la verdadera batalla casi siempre es la configuración.&lt;/p&gt;

&lt;p&gt;¿Te ha pasado? Quieres jugar un Survival con amigos, buscas "mejor configuración paper 1.21" y terminas con 20 pestañas abiertas y tres wikis que se contradicen.&lt;/p&gt;

&lt;h2&gt;
  
  
  La Iniciativa: Basta de Caos
&lt;/h2&gt;

&lt;p&gt;Cansado de copiar y pegar las mismas líneas de configuración una y otra vez, se me ocurrió armar &lt;strong&gt;&lt;a href="https://github.com/Alpha018/paper-config-optimized" rel="noopener noreferrer"&gt;Paper Config Optimized&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No quería hacer "otra guía más". Quería un lugar donde centralizar de verdad las mejores prácticas de la comunidad en archivos listos para usar. De hecho, me he vuelto tan fan de esta "infraestructura como código" que hace poco hice mi primera contribución al repositorio oficial de la imagen de Docker que usamos (PR #3076 en &lt;code&gt;itzg/docker-minecraft-server&lt;/code&gt;) y planeo seguir aportando para mejorar el ecosistema.&lt;/p&gt;

&lt;h2&gt;
  
  
  "La Receta" (Configuración)
&lt;/h2&gt;

&lt;p&gt;Te dejo por aquí la configuración que uso siempre. Es básicamente mi plantilla de "copiar, pegar y olvidarse".&lt;/p&gt;

&lt;p&gt;Lo importante aquí es que le estamos diciendo a Docker dos cosas clave:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"Usa Aikar's Flags"&lt;/strong&gt;: Para que Java no se ponga caprichoso con la memoria.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Descargar las configs del repositorio"&lt;/strong&gt;: Para que el juego corra fluido sin tener que tocar nada.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;itzg/minecraft-server&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paper-lite&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;25565:25565"&lt;/span&gt;

    &lt;span class="c1"&gt;# Consola interactiva (nunca está de más para ver qué pasa)&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Papeleo burocrático de Mojang y elección del software (Paper vuela)&lt;/span&gt;
      &lt;span class="na"&gt;EULA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TRUE"&lt;/span&gt;
      &lt;span class="na"&gt;TYPE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PAPER"&lt;/span&gt;

      &lt;span class="c1"&gt;# Siempre a la última. Vivir al límite.&lt;/span&gt;
      &lt;span class="na"&gt;VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LATEST"&lt;/span&gt;

      &lt;span class="c1"&gt;# Activamos las flags de Aikar.&lt;/span&gt;
      &lt;span class="c1"&gt;# Créeme, no quieres gestionar el Garbage Collector manualmente.&lt;/span&gt;
      &lt;span class="na"&gt;USE_AIKAR_FLAGS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;

      &lt;span class="c1"&gt;# Zona horaria (cámbiala a la tuya para no volverte loco con los logs)&lt;/span&gt;
      &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;America/Santiago"&lt;/span&gt;

      &lt;span class="c1"&gt;# ✨ LA MAGIA:&lt;/span&gt;
      &lt;span class="c1"&gt;# Inyecta las configs optimizadas del repositorio automáticamente al iniciar.&lt;/span&gt;
      &lt;span class="na"&gt;PAPER_CONFIG_REPO&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://raw.githubusercontent.com/Alpha018/paper-config-optimized/main"&lt;/span&gt;

    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./minecraft-data:/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ¿Cómo correr esto?
&lt;/h3&gt;

&lt;p&gt;Es tan simple como meter eso en un archivo &lt;code&gt;docker-compose.yml&lt;/code&gt;, abrir una terminal en la carpeta y tirar un:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚖️ Balance: Pros y Contras
&lt;/h2&gt;

&lt;p&gt;Como en todo en ingeniería, no hay balas de plata. Esta configuración es mi favorita, pero hay que ser transparentes:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Lo Bueno (Pros)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Velocidad&lt;/strong&gt;: Pasas de "cero" a "servidor optimizado" en 2 minutos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mantenimiento&lt;/strong&gt;: Al usar &lt;code&gt;PAPER_CONFIG_REPO&lt;/code&gt;, si mañana salen nuevas mejoras de rendimiento, solo tienes que reiniciar el contenedor para aplicarlas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estabilidad&lt;/strong&gt;: Usar las &lt;em&gt;Aikar's Flags&lt;/em&gt; elimina el 90% de los tirones de lag por culpa de Java.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Lo Malo (Contras)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caja Negra&lt;/strong&gt;: Si te gusta ajustar cada milímetro de la &lt;code&gt;view-distance&lt;/code&gt; a mano, sentirás que pierdes algo de control (aunque siempre puedes sobrescribir los archivos).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencia&lt;/strong&gt;: Dependes de que mi repositorio (o el fork que te hagas) esté en línea para descargar las configs al inicio.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Para Usuarios Avanzados
&lt;/h2&gt;

&lt;p&gt;Esta configuración es ideal para lo que llamo "Survival Técnico Lite". Pero si estás planeando montar una comunidad pública masiva (50+ jugadores) o quieres entender por qué 12GB de RAM es mi número mágico para servidores grandes, escribí una guía técnica mucho más profunda.&lt;/p&gt;

&lt;p&gt;☕ &lt;strong&gt;&lt;a href="https://buymeacoffee.com/alpha018/papermc-ultra-optimizado-con-docker-una-inmersin-tcnica" rel="noopener noreferrer"&gt;Échale un ojo a la Guía Técnica Avanzada en Buy Me a Coffee&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  💌 Agradecimientos Especiales
&lt;/h3&gt;

&lt;p&gt;A una chica que, sin saberlo, me empujó a seguir creando y programando en mi tiempo libre. A quien alguna vez fue mi chispa: para “YLP”, con gratitud… y con cicatrices que también enseñan.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>minecraft</category>
      <category>performance</category>
      <category>java</category>
    </item>
    <item>
      <title>🚀 Supercarga la Autenticación en NestJS con Firebase</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Sun, 04 Jan 2026 09:13:33 +0000</pubDate>
      <link>https://dev.to/alpha018/supercarga-la-autenticacion-en-nestjs-con-firebase-1kc0</link>
      <guid>https://dev.to/alpha018/supercarga-la-autenticacion-en-nestjs-con-firebase-1kc0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🇺🇸 &lt;a href="https://dev.to/alpha018/integrating-firebase-authentication-into-nestjs-with-nestjs-firebase-auth-55m6"&gt;Read this post in English&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si vienes del mundo de &lt;strong&gt;NestJS&lt;/strong&gt;, te encantan los decoradores, la inyección de dependencias y el tipado fuerte. Pero cuando se trata de integrar &lt;strong&gt;Firebase Authentication&lt;/strong&gt;, a menudo se siente como una tarea pesada: &lt;em&gt;guards repetitivos&lt;/em&gt;, verificación manual de tokens y manejo de roles &lt;em&gt;desordenado&lt;/em&gt; que ensucia nuestros controladores.&lt;/p&gt;

&lt;p&gt;Hoy quiero reintroducir &lt;strong&gt;&lt;code&gt;nestjs-firebase-auth&lt;/code&gt;&lt;/strong&gt;, una librería diseñada para cerrar esa brecha y tratar la autenticación de Firebase con el respeto que merece en una arquitectura moderna de NestJS.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 ¿Por qué creé esto?
&lt;/h2&gt;

&lt;p&gt;Firebase Authentication es robusto, pero el SDK Admin oficial es solo eso: un SDK. No sabe nada sobre Guards, Decoradores o Inyección de Dependencias de NestJS.&lt;/p&gt;

&lt;p&gt;No deberías tener que escribir middleware personalizado para decodificar tokens o verificar claims manualmente en cada controlador. &lt;code&gt;nestjs-firebase-auth&lt;/code&gt; cierra esta brecha, dándote una API simplificada y declarativa para asegurar tu aplicación.&lt;/p&gt;

&lt;p&gt;Con el último lanzamiento &lt;strong&gt;v1.9.0&lt;/strong&gt;, he pulido la experiencia aún más con decoradores más limpios y una mejor integración con otros servicios de Firebase como Firestore.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instalación
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @alpha018/nestjs-firebase-auth firebase-admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🛠️ Profundizando en el Código
&lt;/h2&gt;

&lt;p&gt;Veamos ejemplos más realistas de lo que puedes lograr.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Autenticación Declarativa &lt;code&gt;(@Auth)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Olvídate de importar y vincular manualmente los guards con &lt;code&gt;@UseGuards(FirebaseGuard)&lt;/code&gt;. Ahora tienes un decorador &lt;code&gt;@Auth()&lt;/code&gt; limpio que hace clara tu intención.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth&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="s1"&gt;@alpha018/nestjs-firebase-auth&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="nd"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 🛡️ Protege todo este controlador&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;getProfile&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Estos datos son seguros.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Control de Acceso Basado en Roles (RBAC)
&lt;/h3&gt;

&lt;p&gt;Manejar roles no debería ser complicado. Utilizo los Custom Claims de Firebase para almacenar los roles del usuario, y el decorador &lt;code&gt;@Roles&lt;/code&gt; para hacerlos cumplir sin escribir una sola línea de lógica de validación.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Roles&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="s1"&gt;@alpha018/nestjs-firebase-auth&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="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdminController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADMIN&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="s1"&gt;SUPERUSER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// ⛔ Solo permite usuarios con estos roles&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;getDashboard&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Datos de administración ultra secretos&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; El decorador &lt;code&gt;@Roles&lt;/code&gt; activa automáticamente la lógica de validación de roles subyacente cuando se usa con &lt;code&gt;@Auth&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Integrando con otros servicios de Firebase
&lt;/h3&gt;

&lt;p&gt;¿Necesitas hablar con Firestore o enviar un mensaje FCM específico? Siempre ha sido posible, pero quiero destacar lo fácil que es acceder a la instancia inicializada de &lt;code&gt;admin.app.App&lt;/code&gt; directamente desde mi provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FirebaseProvider&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="s1"&gt;@alpha018/nestjs-firebase-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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getFirestore&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="s1"&gt;firebase-admin/firestore&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="nd"&gt;Injectable&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserDataService&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;firebaseProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FirebaseProvider&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="nf"&gt;saveUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uid&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;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 🔥 Usa la instancia de app del módulo de auth para Firestore&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firestore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getFirestore&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;firebaseProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&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;firestore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set&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="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;
  
  
  🎯 Mejores Casos de Uso
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Aplicaciones SaaS&lt;/strong&gt;: Donde necesitas autenticación robusta y segura con configuración mínima.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microservicios&lt;/strong&gt;: Compartir lógica de autenticación a través de múltiples servicios NestJS fácilmente usando un enfoque de librería compartida.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sistemas RBAC&lt;/strong&gt;: Apps que requieren control granular sobre quién puede hacer qué (Admins vs. Usuarios).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚠️ Trade-offs (Honestidad Ante Todo)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validación Remota&lt;/strong&gt;: Por defecto, verifico los roles obteniendo el registro de usuario desde Firebase. Esto asegura que los roles estén &lt;em&gt;siempre&lt;/em&gt; actualizados (sin tokens obsoletos), pero incurre en una pequeña latencia de red.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validación Local&lt;/strong&gt;: Puedes optar por &lt;code&gt;useLocalRoles: true&lt;/code&gt; para validar contra los claims del token ID directamente (cero latencia), pero debes aceptar que los cambios de roles efectivamente se actualizan solo cuando el usuario refresca su token.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💌 Agradecimientos Especiales
&lt;/h2&gt;

&lt;p&gt;Este proyecto ha sido construido con muchas horas de dedicación y amor por el código.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Para una chica que siempre me inspira en silencio, y su sola existencia me incentiva a seguir programando en mis tiempos libres, gracias "YLP".&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ☕ Apoya el Proyecto
&lt;/h2&gt;

&lt;p&gt;Si encontraste este proyecto útil o interesante, por favor considera apoyarlo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Estrella en GitHub&lt;/strong&gt;: Es gratis y ayuda a otros a encontrar la librería.&lt;/li&gt;
&lt;li&gt;☕ &lt;strong&gt;Cómprame un Café&lt;/strong&gt;: Si quieres ir un paso más allá, puedes hacerlo en &lt;a href="https://buymeacoffee.com/alpha018" rel="noopener noreferrer"&gt;buymeacoffee.com/alpha018&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔗 Pruébalo y Contribuye
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/Alpha018/nestjs-firebase-auth" rel="noopener noreferrer"&gt;github.com/Alpha018/nestjs-firebase-auth&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autor&lt;/strong&gt;: &lt;a href="https://dev.to/alpha018"&gt;dev.to/alpha018&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;¿Qué piensas? ¿Estás listo para simplificar tu flujo de autenticación? 👇&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>typescript</category>
      <category>nestjs</category>
      <category>security</category>
    </item>
    <item>
      <title>🚀 NestJS + RedisOM: Elevando el manejo de Estado a otro nivel</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Sat, 03 Jan 2026 21:19:40 +0000</pubDate>
      <link>https://dev.to/alpha018/nestjs-redisom-elevando-el-manejo-de-estado-a-otro-nivel-46bo</link>
      <guid>https://dev.to/alpha018/nestjs-redisom-elevando-el-manejo-de-estado-a-otro-nivel-46bo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🇺🇸 &lt;a href="https://dev.to/alpha018/nestjs-redisom-taking-state-management-to-the-next-level-5gli"&gt;Read this post in English&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si vienes del mundo de &lt;strong&gt;NestJS&lt;/strong&gt;, amas los decoradores, la inyección de dependencias y el tipado fuerte. Pero cuando toca integrar &lt;strong&gt;Redis&lt;/strong&gt;, a menudo sentimos que retrocedemos 10 años: &lt;em&gt;magic strings&lt;/em&gt;, parsers manuales de JSON y comandos &lt;em&gt;low-level&lt;/em&gt; que ensucian nuestra lógica de negocio.&lt;/p&gt;

&lt;p&gt;Hoy quiero presentarles &lt;strong&gt;&lt;code&gt;nestjs-redisom&lt;/code&gt;&lt;/strong&gt;, una librería open-source diseñada para cerrar esa brecha y tratar a Redis con el respeto que se merece en una arquitectura empresarial moderna.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 ¿Por qué creé esto?
&lt;/h2&gt;

&lt;p&gt;Redis ha evolucionado. Ya no es solo un caché key-value. Con &lt;strong&gt;Redis Stack&lt;/strong&gt; (RedisJSON + RediSearch), se ha convertido en una base de datos de documentos en tiempo real increíblemente potente.&lt;/p&gt;

&lt;p&gt;Sin embargo, usar estas features desde Node.js suele ser verboso. &lt;strong&gt;Redis OM&lt;/strong&gt; (la librería oficial) ayuda, pero integrarla en NestJS requería mucho &lt;em&gt;boilerplate&lt;/em&gt;. &lt;code&gt;nestjs-redisom&lt;/code&gt; elimina esa fricción, dándote una experiencia nativa.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instalación
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nestjs-redisom redis-om redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🛠️ Profundizando en el Código
&lt;/h2&gt;

&lt;p&gt;Veamos ejemplos más realistas de lo que puedes lograr.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Modelado de Datos Complejos (Anidados)
&lt;/h3&gt;

&lt;p&gt;No estamos limitados a strings planos. Podemos modelar objetos complejos con validación implícita.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Definimos una clase anidada&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;indexed&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="nx"&gt;city&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="c1"&gt;// Indexado para buscar "Usuarios en Madrid"&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;zipCode&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="c1"&gt;// 2. Definimos la Entidad Principal&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Schema&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;textSearch&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="nx"&gt;bio&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="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&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="nx"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// Mapeo anidado&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;roles&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="c1"&gt;// Arrays de strings&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Búsquedas Avanzadas (Fluent API)
&lt;/h3&gt;

&lt;p&gt;Aquí es donde &lt;code&gt;nestjs-redisom&lt;/code&gt; brilla. Olvídate de aprender la sintaxis de comandos de RediSearch (&lt;code&gt;FT.SEARCH index "@field:value ..."&lt;/code&gt;). Usa esta API fluida y tipada.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Encuentra usuarios que vivan en 'New York', tengan el rol 'admin' y cuya bio mencione 'typescript'"&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&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;userRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;address_city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Busca en objeto anidado&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;roles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;// Busca en Array&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typescript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;// Full-Text Search&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="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                      &lt;span class="c1"&gt;// Paginación nativa&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Expiración de Documentos (TTL)
&lt;/h3&gt;

&lt;p&gt;Manejar sesiones o caché temporal es trivial. No necesitas scripts de Lua ni nada por el estilo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Crea una sesión y haz que expire en 1 hora&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createSession&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;User&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;session&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="nx"&gt;sessionRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&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="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="nx"&gt;sessionRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&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;sessionRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idField&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3600&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;session&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;
  
  
  🎯 Mejores Casos de Uso
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;High-Speed Objects&lt;/strong&gt;: Carritos de compra, Perfiles de Jugador, Sesiones. Datos que lees/escribes 1000 veces por segundo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caché con "Poderes"&lt;/strong&gt;: A veces necesitas invalidar caché por criterio (&lt;em&gt;"borrar caché de todos los productos de la categoría X"&lt;/em&gt;). Con Redis normal es imposible (o lento con &lt;code&gt;KEYS *&lt;/code&gt;). Con RedisOM es una &lt;em&gt;query&lt;/em&gt; de O(1) o O(N log N).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geo-Spatial Features&lt;/strong&gt;: "Encuentra conductores cerca de mi", usando la potencia nativa de Redis para geo-hashing.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚠️ Trade-offs (Honestidad ante todo)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAM es Dinero&lt;/strong&gt;: Redis guarda todo en memoria. No uses esto para guardar logs de auditoría de 5 años.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eventual Consistency&lt;/strong&gt;: Los índices de búsqueda se actualizan asíncronamente (milisegundos). No es ACID estricto para búsquedas inmediatas post-escritura.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💌 Agradecimientos Especiales
&lt;/h2&gt;

&lt;p&gt;Este proyecto ha sido construido con muchas horas de dedicación y amor por el código.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Para una chica que siempre me inspira en silencio, y su sola existencia me incentiva a seguir programando en mis tiempos libres, gracias "YLP".&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ☕ Apoya el Proyecto
&lt;/h2&gt;

&lt;p&gt;Si este proyecto te ha sido útil o te parece interesante, considera apoyarlo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Star en GitHub&lt;/strong&gt;: Es gratis y ayuda a que más gente lo encuentre.&lt;/li&gt;
&lt;li&gt;☕ &lt;strong&gt;Invítame un Café&lt;/strong&gt;: Si quieres ir un paso más allá, puedes hacerlo en &lt;a href="https://buymeacoffee.com/alpha018" rel="noopener noreferrer"&gt;buymeacoffee.com/alpha018&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔗 Pruébalo y Contribuye
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/Alpha018/nestjs-redisom" rel="noopener noreferrer"&gt;github.com/Alpha018/nestjs-redisom&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autor&lt;/strong&gt;: &lt;a href="https://dev.to/alpha018"&gt;dev.to/alpha018&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;¿Qué opinan? ¿Le darán una oportunidad a Redis como base de datos de documentos? 👇&lt;/p&gt;

</description>
      <category>redis</category>
      <category>typescript</category>
      <category>node</category>
      <category>nestjs</category>
    </item>
    <item>
      <title>🚀 NestJS + RedisOM: Taking State Management to the Next Level</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Fri, 02 Jan 2026 01:39:59 +0000</pubDate>
      <link>https://dev.to/alpha018/nestjs-redisom-taking-state-management-to-the-next-level-5gli</link>
      <guid>https://dev.to/alpha018/nestjs-redisom-taking-state-management-to-the-next-level-5gli</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🇪🇸 &lt;a href="https://dev.to/alpha018/nestjs-redisom-elevando-el-manejo-de-estado-a-otro-nivel-46bo"&gt;Lee este post en Español&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you come from the &lt;strong&gt;NestJS&lt;/strong&gt; world, you love decorators, dependency injection, and strong typing. But when it comes to integrating &lt;strong&gt;Redis&lt;/strong&gt;, it often feels like we're stepping back 10 years: &lt;em&gt;magic strings&lt;/em&gt;, manual JSON parsers, and &lt;em&gt;low-level&lt;/em&gt; commands that clutter our business logic.&lt;/p&gt;

&lt;p&gt;Today I want to introduce &lt;strong&gt;&lt;code&gt;nestjs-redisom&lt;/code&gt;&lt;/strong&gt;, an open-source library designed to bridge that gap and treat Redis with the respect it deserves in a modern enterprise architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 Why did I create this?
&lt;/h2&gt;

&lt;p&gt;Redis has evolved. It is no longer just a key-value cache. With &lt;strong&gt;Redis Stack&lt;/strong&gt; (RedisJSON + RediSearch), it has become an incredibly powerful real-time document database.&lt;/p&gt;

&lt;p&gt;However, using these features from Node.js is often verbose. &lt;strong&gt;Redis OM&lt;/strong&gt; (the official library) helps, but integrating it into NestJS required a lot of &lt;em&gt;boilerplate&lt;/em&gt;. &lt;code&gt;nestjs-redisom&lt;/code&gt; removes that friction, giving you a native experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nestjs-redisom redis-om redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🛠️ Deep Dive into the Code
&lt;/h2&gt;

&lt;p&gt;Let's look at more realistic examples of what you can achieve.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Complex Data Modeling (Nested)
&lt;/h3&gt;

&lt;p&gt;We aren't limited to flat strings. We can model complex objects with implicit validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Define a Nested Class&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;indexed&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="nx"&gt;city&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="c1"&gt;// Indexed to search "Users in Madrid"&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;zipCode&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="c1"&gt;// 2. Define the Main Entity&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Schema&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;textSearch&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="nx"&gt;bio&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="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&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="nx"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// Nested mapping&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;roles&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="c1"&gt;// Array of strings&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Advanced Searching (Fluent API)
&lt;/h3&gt;

&lt;p&gt;This is where &lt;code&gt;nestjs-redisom&lt;/code&gt; shines. Forget about learning RediSearch command syntax (&lt;code&gt;FT.SEARCH index "@field:value ..."&lt;/code&gt;). Use this fluent, typed API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Find users living in 'New York', having the role 'admin', and whose bio mentions 'typescript'"&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&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;userRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;address_city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Search in nested object&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;roles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;// Search in Array&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typescript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;// Full-Text Search&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="nf"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                      &lt;span class="c1"&gt;// Native pagination&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Document Expiration (TTL)
&lt;/h3&gt;

&lt;p&gt;Handling sessions or temporary cache is trivial. You don't need Lua scripts or anything like that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a session and make it expire in 1 hour&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createSession&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;User&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;session&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="nx"&gt;sessionRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&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="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="nx"&gt;sessionRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&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;sessionRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idField&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3600&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;session&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;
  
  
  🎯 Best Use Cases
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;High-Speed Objects&lt;/strong&gt;: Shopping Carts, Player Profiles, Sessions. Data you read/write 1000 times per second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Powered-Up Cache&lt;/strong&gt;: Sometimes you need to invalidate cache by criteria (&lt;em&gt;"clear cache for all products in category X"&lt;/em&gt;). With standard Redis, this is impossible (or slow with &lt;code&gt;KEYS *&lt;/code&gt;). With RedisOM, it's an O(1) or O(N log N) &lt;em&gt;query&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geo-Spatial Features&lt;/strong&gt;: "Find drivers near me", using Redis native power for geo-hashing.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚠️ Trade-offs (Honesty First)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAM is Money&lt;/strong&gt;: Redis stores everything in memory. Don't use this to store 5 years of audit logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eventual Consistency&lt;/strong&gt;: Search indexes update asynchronously (milliseconds). It is not strict ACID for immediate post-write searches.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💌 Special Acknowledgments
&lt;/h2&gt;

&lt;p&gt;This project has been built with many hours of dedication and love for code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;To a girl who always inspires me in silence, and whose mere existence incentivizes me to keep programming in my free time, thank you "YLP".&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ☕ Support the Project
&lt;/h2&gt;

&lt;p&gt;If you found this project useful or interesting, please consider supporting it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Star on GitHub&lt;/strong&gt;: It's free and helps others find the library.&lt;/li&gt;
&lt;li&gt;☕ &lt;strong&gt;Buy me a Coffee&lt;/strong&gt;: If you want to go the extra mile, you can do so at &lt;a href="https://buymeacoffee.com/alpha018" rel="noopener noreferrer"&gt;buymeacoffee.com/alpha018&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔗 Try it and Contribute
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/Alpha018/nestjs-redisom" rel="noopener noreferrer"&gt;github.com/Alpha018/nestjs-redisom&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt;: &lt;a href="https://dev.to/alpha018"&gt;dev.to/alpha018&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What do you think? Will you give Redis a chance as a document database? 👇&lt;/p&gt;

</description>
      <category>redis</category>
      <category>typescript</category>
      <category>node</category>
      <category>nestjs</category>
    </item>
    <item>
      <title>🚀 Supercharge NestJS Authentication with Firebase</title>
      <dc:creator>Tomás Alegre Sepúlveda</dc:creator>
      <pubDate>Tue, 10 Sep 2024 22:56:14 +0000</pubDate>
      <link>https://dev.to/alpha018/integrating-firebase-authentication-into-nestjs-with-nestjs-firebase-auth-55m6</link>
      <guid>https://dev.to/alpha018/integrating-firebase-authentication-into-nestjs-with-nestjs-firebase-auth-55m6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🇪🇸 &lt;a href="https://dev.to/alpha018/supercarga-la-autenticacion-en-nestjs-con-firebase-1kc0"&gt;Lee este post en Español&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're building a modern application with NestJS, you probably love its modularity, decorators, and type safety. But when it comes to authentication, integrating external providers often feels like a chore—boilerplate guards, manual token verification, and messy role handling.&lt;/p&gt;

&lt;p&gt;Today, I want to re-introduce &lt;code&gt;nestjs-firebase-auth&lt;/code&gt;, a library designed to make Firebase Authentication feel native to the NestJS ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 Why this library?
&lt;/h2&gt;

&lt;p&gt;Firebase Authentication is robust, but the official Admin SDK is just that—an SDK. It doesn't know about NestJS Guards, Decorators, or Dependency Injection.&lt;/p&gt;

&lt;p&gt;You shouldn't have to write custom middleware to decode tokens or manually check claims in every controller. &lt;code&gt;nestjs-firebase-auth&lt;/code&gt; bridges this gap, giving you a simplified, declarative API for securing your application.&lt;/p&gt;

&lt;p&gt;With the latest &lt;strong&gt;v1.9.0&lt;/strong&gt; release, I've polished the experience even further with cleaner decorators and better integration with other Firebase services like Firestore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @alpha018/nestjs-firebase-auth firebase-admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🛠️ Deep Dive into the Code
&lt;/h2&gt;

&lt;p&gt;Let's look at how this changes your daily workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Declarative Authentication (&lt;code&gt;@Auth&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Forget about manually importing and binding guards with &lt;code&gt;@UseGuards(FirebaseGuard)&lt;/code&gt;. You now have a clean &lt;code&gt;@Auth()&lt;/code&gt; decorator that makes your intention clear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth&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="s1"&gt;@alpha018/nestjs-firebase-auth&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="nd"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 🛡️ Protects this entire controller&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;getProfile&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This data is secure.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Role-Based Access Control (RBAC)
&lt;/h3&gt;

&lt;p&gt;Managing roles shouldn't be complicated. I use Firebase Custom Claims to store user roles, and the &lt;code&gt;@Roles&lt;/code&gt; decorator to enforce them without writing a single line of validation logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Roles&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="s1"&gt;@alpha018/nestjs-firebase-auth&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="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdminController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Roles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADMIN&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="s1"&gt;SUPERUSER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// ⛔ Only allows users with these roles&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;getDashboard&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Top secret admin data&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;@Roles&lt;/code&gt; decorator automatically triggers the underlying role validation logic when used with &lt;code&gt;@Auth&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Type-Safe User Access
&lt;/h3&gt;

&lt;p&gt;Accessing the current user's data is straightforward and type-safe using &lt;code&gt;@FirebaseUser&lt;/code&gt;.&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;FirebaseUser&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;DecodedIdToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// `user` is the standard Firebase DecodedIdToken&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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;uid&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;email&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;
  
  
  4. Integrating with other Firebase Services
&lt;/h3&gt;

&lt;p&gt;Need to talk to Firestore or send a clear-cut FCM message? It has always been possible, but I want to highlight how easy it is to access the initialized &lt;code&gt;admin.app.App&lt;/code&gt; instance directly from my provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FirebaseProvider&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="s1"&gt;@alpha018/nestjs-firebase-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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getFirestore&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="s1"&gt;firebase-admin/firestore&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="nd"&gt;Injectable&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;class&lt;/span&gt; &lt;span class="nc"&gt;UserDataService&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;firebaseProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FirebaseProvider&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="nf"&gt;saveUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uid&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;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 🔥 Use the auth module's app instance for Firestore&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firestore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getFirestore&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;firebaseProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&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;firestore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set&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="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;
  
  
  🎯 Best Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SaaS Applications&lt;/strong&gt;: Where you need robust, secure authentication with minimal setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microservices&lt;/strong&gt;: Sharing authentication logic across multiple NestJS services easily using a shared library approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RBAC Systems&lt;/strong&gt;: Apps that require granular control over who can do what (Admins vs. Users).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚠️ Trade-offs (Honesty First)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Remote Validation&lt;/strong&gt;: By default, I verify roles by fetching the user record from Firebase. This ensures roles are &lt;em&gt;always&lt;/em&gt; up to date (no stale tokens), but it incurs a small network latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Validation&lt;/strong&gt;: You can opt-in to &lt;code&gt;useLocalRoles: true&lt;/code&gt; to validate against the ID token's claims directly (zero latency), but you must accept that role changes effectively update only when the user refreshes their token.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💌 Special Acknowledgments
&lt;/h2&gt;

&lt;p&gt;This project has been built with many hours of dedication and love for code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;To a girl who always inspires me in silence, and whose mere existence incentivizes me to keep programming in my free time, thank you "YLP".&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ☕ Support the Project
&lt;/h2&gt;

&lt;p&gt;If you found this project useful or interesting, please consider supporting it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐️ &lt;strong&gt;Star on GitHub&lt;/strong&gt;: It's free and helps others find the library.&lt;/li&gt;
&lt;li&gt;☕ &lt;strong&gt;Buy me a Coffee&lt;/strong&gt;: If you want to go the extra mile, you can do so at &lt;a href="https://buymeacoffee.com/alpha018" rel="noopener noreferrer"&gt;buymeacoffee.com/alpha018&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔗 Try it and Contribute
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/Alpha018/nestjs-firebase-auth" rel="noopener noreferrer"&gt;github.com/Alpha018/nestjs-firebase-auth&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt;: &lt;a href="https://dev.to/alpha018"&gt;dev.to/alpha018&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What do you think? Are you ready to simplify your authentication flow? 👇&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>typescript</category>
      <category>nestjs</category>
      <category>security</category>
    </item>
  </channel>
</rss>
