AI 资讯
Designing Reliable APIs for Production Applications: Lessons From Building Real-World Digital Products
Designing Reliable APIs for Production Applications: Lessons From Building Real-World Digital Products APIs are often described as the “bridge” between different parts of an application, but building a production-ready API involves much more than sending data from a frontend to a backend. Through my experience building full-stack applications, I've learned that a good API needs to be designed around reliability, security, maintainability and the actual needs of its users. Here are some of the principles I now consider when designing APIs: Design around resources, not screens An API shouldn't simply mirror the frontend interface. It should expose meaningful resources and operations that can evolve independently from the UI. Validate everything at the API boundary Data coming from a client should never be trusted automatically. Request validation, type checking and clear error responses help prevent invalid data from propagating through the system. Authentication is only the beginning An authenticated user should not automatically have access to every resource. APIs need appropriate authorisation and access-control rules for sensitive operations. Design predictable errors A useful API doesn't just return “something went wrong.” Clients need consistent status codes and structured error responses so that applications can respond appropriately. Think about idempotency This becomes particularly important when an API handles operations such as payments, orders or other actions that shouldn't accidentally happen twice because of a network retry. Don't expose unnecessary data APIs should return what the client needs rather than exposing entire database records. This reduces unnecessary data transfer and can also reduce the risk of accidentally exposing sensitive information. Logging and observability matter An API can appear perfect during development and still fail in production. Good logging and monitoring make it possible to understand what happened when requests fail, la
AI 资讯
The Rate Limiter Strikes Back: Designing a Token Bucket from Scratch
The Quest Begins (The "Why") I still remember the first time our API started choking under a sudden traffic spike. It was a Friday afternoon, the kind where you’re just about to log off, and the monitoring dashboard lit up like a Christmas tree. Requests were piling up, latency shot through the roof, and our users began seeing those dreaded “429 Too Many Requests” errors. We had a naive rate limiter in place—a simple fixed‑window counter that reset every minute. It worked fine when traffic was steady, but as soon as a burst hit, the counter would either let too many through (because we hadn’t hit the limit yet) or block everything for the whole minute (because we’d already exhausted the quota). It felt like trying to hold back a tsunami with a sandbag. Honestly, I was frustrated. I knew there had to be a smarter way to smooth out those bursts without penalizing honest users or over‑protecting the system. That’s when I dove into the world of rate‑limiting algorithms, and the token bucket caught my eye like a shiny loot drop in a dungeon. The Revelation (The Insight) The token bucket is deceptively simple, yet it solves the exact pain points we were experiencing. Imagine a bucket that holds a fixed number of tokens. Tokens drip into the bucket at a steady rate (say, 10 tokens per second). Each incoming request consumes a token. If the bucket is empty, the request is denied or delayed; if there’s a token, the request proceeds and the token is removed. Why does this beat the fixed‑window counter? Burst tolerance – The bucket can store up to its capacity, allowing a short burst of requests up to that limit without waiting for the next window. Smooth throttling – Because tokens are added continuously, the limiter adapts to the actual request rate rather than resetting abruptly at arbitrary intervals. Memory‑light – We only need to track two numbers: the current token count and the last time we refilled the bucket. No arrays of timestamps per key. Here’s a quick ASCII sket
AI 资讯
Building a Production ML Trading Dashboard with the Dhan API
Real integration notes for wiring NIFTY ML models to live broker data via Dhan. Research/ paper-trading context — not a live-trading recommendation. Why Dhan Dhan's API exposes direct option-chain access — exactly what an options-ML system needs: POST /optionchain — full chain for an underlying POST /optionchain/expirylist — available expiries Fields: security_id , last_price , volume , oi , previous_oi , implied_volatility , top_bid_price , top_ask_price , and greeks (delta/theta/gamma/vega) Security IDs are stable: NIFTY = 13 (IDX_I) , BANKNIFTY = 10001 (IDX_I) . The Pipeline Shape A research dashboard pulls live chain + underlying, runs the trained XGBoost model on each new 15-minute bar, and displays: side score (CE/PE alignment) gate state (entry ready / blocked) contract quality scores a doctrine/backtest report Keep the inference path separate from the execution path . The dashboard shows; a permissioned, human-approved module places orders. Paper Trade First The DhanLiveTrader pattern: load the model, predict on each new bar, place long orders with configurable SL/TP (default 1.0 ATR SL, 2.0 ATR TP), and run in paper mode first . Only after stable out-of-sample + paper evidence should any execution module even be considered. { "client_id" : "YOUR_DHAN_CLIENT_ID" , "access_token" : "YOUR_DHAN_ACCESS_TOKEN" , "is_paper_trade" : true , "nifty_symbol" : "NIFTY" , "quantity" : 50 , "max_trades_per_day" : 3 , "sl_atr_mult" : 1.0 , "tp_atr_mult" : 2.0 } The Hard Part: Stops A known footgun: using a Stop-Loss Limit (SL-L) order with price = sl − 0.05 means it won't fill if price crashes through the stop. Prefer SL-Market for the protective stop. Execution quality is its own research topic — don't bolt it on at the end. Honest Status The ML side of this stack showed real directional skill (60.5% top-decile accuracy) but the fixed-SL backtest was still unprofitable (PF 0.53). A dashboard that displays an honest "RESEARCH / PAPER" status is worth more than one that hid
AI 资讯
Your AI Agent Scheduler Needs a Clock-Skew Budget, Not Just Cron
A scheduler can be perfectly healthy and still run the wrong job at the wrong time. The failure is usually not the cron expression. It is the boundary between wall-clock time, monotonic elapsed time, leases, retries, and a process that may pause or restart. A reliable agent scheduler needs an explicit clock contract. Without one, a clock correction can make a job run twice, never run, or run after its authorization window has expired. The three clocks an agent should not conflate Use wall-clock time for human meaning and durable records: scheduled_at: when the user asked for the run not_before: the earliest acceptable dispatch time expires_at: the latest acceptable dispatch time Use a monotonic clock for elapsed-time decisions inside one process: lease renewal deadlines backoff timers watchdog intervals drain deadlines Use a database or provider sequence for ordering across processes: scheduler ownership fencing tokens attempt numbers reconciliation order A monotonic timestamp cannot be compared across hosts, and a wall-clock timestamp cannot safely measure a five-minute lease if NTP steps the clock backward. Store both kinds of evidence instead of pretending one timestamp answers every question. A small scheduling contract Here is a deliberately boring record shape: action: send_digest run_id: 01J... scheduled_at: 2026-08-19T08:00:00Z not_before: 2026-08-19T08:00:00Z expires_at: 2026-08-19T08:05:00Z lease_owner: worker-7 lease_token: 1842 attempt: 1 state: READY The important part is not the field names. It is the decision rule: The scheduler claims the run with a durable lease and fencing token. It checks wall-clock eligibility against not_before and expires_at. The worker checks that its lease token is still current before starting. The effect layer checks the token again before a side effect. If the outcome is ambiguous, record UNKNOWN and reconcile by the provider's idempotency key instead of blindly retrying. That last step matters after restarts. A clean rest
AI 资讯
Prisma Studio is not an admin panel
If you build with Prisma, you already know Prisma Studio. Run one command and you get a clean, visual way to browse and edit rows in your database. It's genuinely useful, and I reach for it every day while developing. But somewhere between "I need to look at my data" and "I need to let a support agent safely edit a customer's record in production," Prisma Studio quietly stops being the right tool. It was never trying to be that tool. It's a database viewer. An admin panel is something else, and the gap between the two is exactly the part that matters once real people and real permissions are involved. I ended up building a small package to fill that gap for my own Express + Prisma apps. Writing it forced me to be precise about what an admin panel actually adds on top of a database browser. Here's the distinction as I now understand it. A database browser shows rows. An admin panel governs them. Prisma Studio connects to your database and shows you everything. That's the point of it, and it's also why you'd never hand it to a non-engineer or expose it in production. It has no concept of who is looking, what they're allowed to do, or which rows they're allowed to touch. An admin panel's whole job is those three questions. The package I built mounts a React UI at /admin and a guarded JSON API under /admin/api/* on your existing Express app. Every single request through that API runs the same pipeline, in the same order: authentication → permission check → tenant scope → validation → Prisma mutation/query → optional audit event That ordering is the entire difference. A database browser skips straight to the mutation. An admin panel refuses to run the mutation until it knows the request is authenticated, permitted, scoped to the right tenant, and valid. Permissions and scope are two different questions This was the design decision I care most about, because collapsing these two into one is how data leaks happen. Permissions decide which actions a role may take. Can an ed
AI 资讯
Mongodb Partitioning
At Whoz , we build a SaaS platform that helps professional services companies manage their talent staffing. At the heart of our product lies a concept called a worklog — a record of time spent by a user on a given activity. Every consultant, every day, on every project, generates worklogs. It sounds simple. And for years, it was. Then the numbers caught up with us. The Problem: A Collection That Never Stops Growing Our worklog MongoDB collection had reached 530 million documents , representing just over 32 GB of data. And the growth rate was accelerating — not just because we were onboarding more clients, but because users were increasingly splitting their activity into finer-grained entries, generating more worklogs per person per day than ever before. A worklog document looks roughly like this: { "date" : "2024-03-15" , "talentId" : "abc123" , "workspaceId" : "ws456" , "duration" : 0.5 , "activityType" : "TASK" , "taskId" : "task789" } Simple enough. But at 530 million of them, even the most routine operations become painful: Backup : nearly 1 hour Restore : up to 4 hours Schema migrations : we hadn't dared run one at full scale yet — and that alone was a warning sign Every year, the collection grows faster than the year before. The backup and restore windows were becoming operationally risky. We needed to act. Exploring Our Options We identified three potential approaches before settling on a solution. Option 1 — MongoDB Sharding Sharding is MongoDB's native horizontal scaling mechanism. It distributes a collection across multiple shards, each backed by its own replica set. On paper, it looked like a match. In practice, we ran into a fundamental mismatch with our actual needs. Our core issue wasn't query throughput — worklogs from three years ago are rarely queried, and when they are, performance expectations are low. Our issue was operational overhead : backup time, restore time, and the cost of running large batch operations over the full dataset. Sharding woul
AI 资讯
Your Database Is Making 4 Promises. Here's What ACID Means.
Introduction Your program keeps opening transactions. A signup writes a new user row. A checkout debits one account and credits another. A form submission updates three related tables at once. You wrap it all in BEGIN and COMMIT and move on, trusting that the database will handle whatever happens in between. Most of the time it does. But what is it actually promising you when it handles that? And what does it have to do behind the scenes to keep that promise? Say a user transfers ₹1,000 from Account A to Account B. The application runs two updates: subtract 1,000 from A, add 1,000 to B. Now say the server crashes right after the first update runs but before the second one does. Account A: -₹1,000 Account B: +₹0 That money didn't move. It vanished. No error message fixes that, and no user accepts "the server restarted" as an explanation for their missing balance. This is the exact problem a set of guarantees called ACID was built to solve. Most developers can recite the acronym, Atomicity, Consistency, Isolation, Durability, without being able to explain what any of the four words actually promise, or what the database has to do internally to keep those promises. This article tries to fix that. -- 1. What Is a Transaction? Before ACID makes sense, you need to understand what a transaction actually is. A transaction is a group of one or more database operations treated as a single logical unit of work. Either the whole group succeeds, or none of it does. The bank transfer above is a textbook transaction: two updates that only make sense together. In SQL, a transaction usually looks like this: BEGIN ; UPDATE accounts SET balance = balance - 1000 WHERE id = 1 ; UPDATE accounts SET balance = balance + 1000 WHERE id = 2 ; COMMIT ; BEGIN tells the database "everything from here on is one unit." COMMIT tells it "we're done, make it permanent." If something goes wrong in between, a constraint violation, a crash, the application deciding to cancel, the database can issue a RO
AI 资讯
Você criou uma tabela de tokens pra proteger PDF. O Laravel já fazia isso.
O contrato do cliente tá numa URL que qualquer um adivinha A tarefa parecia simples: o cliente precisa baixar a nota fiscal dele. Você salvou em storage/app/public/notas/ , rodou php artisan storage:link , mandou o link e foi feliz. https://app.com/storage/notas/nota-1042.pdf . Semanas depois cai a ficha. Aquele arquivo está aberto na internet . Sem login, sem nada. E o nome é sequencial: quem baixou a nota-1042.pdf só precisa de curiosidade e cinco segundos pra tentar a 1041 . E a 1040 . Então você faz a coisa certa: tira do disco público e cria um sistema pra controlar acesso. Tabela download_tokens , model, geração de UUID, coluna expires_at , controller que valida, e um comando no scheduler pra limpar os vencidos. Sessenta linhas depois, funciona. E aí alguém comenta no PR: "por que você não usou uma URL assinada?" O sistema que você não precisava construir // ❌ migration + model + controller + command. tudo isso pra um PDF. Schema :: create ( 'download_tokens' , function ( Blueprint $table ) { $table -> id (); $table -> uuid ( 'token' ) -> unique (); $table -> string ( 'path' ); $table -> foreignId ( 'user_id' ); $table -> timestamp ( 'expires_at' ); $table -> timestamps (); }); public function gerarLink ( NotaFiscal $nota ): string { $token = DownloadToken :: create ([ 'token' => Str :: uuid (), 'path' => $nota -> arquivo_path , 'user_id' => auth () -> id (), 'expires_at' => now () -> addMinutes ( 10 ), ]); return route ( 'download' , $token -> token ); } Não tem nada de errado tecnicamente. O problema é o custo: mais uma tabela crescendo pra sempre, mais um comando no scheduler, mais um caminho pra testar. E você vai manter isso enquanto o projeto existir. O Laravel resolve o mesmo problema com uma assinatura criptográfica na própria URL. Sem estado, sem tabela, sem limpeza. Como uma URL assinada funciona A ideia é bonita de simples: o Laravel monta a URL com os parâmetros que você quer, calcula um hash disso tudo usando a APP_KEY e cola o hash no final. /not
AI 资讯
Seu log tem 40 mil linhas e nenhuma resposta
"Deu erro ao salvar, umas duas da tarde" É a única informação que você tem. O cliente não lembra o que clicou, não tirou print e já fechou a aba. Você abre o laravel.log . Quarenta mil linhas no dia. Faz um grep por "erro". Aparecem 1.200 ocorrências, e a maioria é isso: [2026-08-14 14:03:11] production.INFO: entrou [2026-08-14 14:03:11] production.INFO: erro aqui [2026-08-14 14:03:12] production.INFO: passou [2026-08-14 14:03:12] production.ERROR: Erro ao salvar Erro ao salvar o quê ? De qual usuário? Qual pedido? Qual valor? Aquele entrou da linha de cima é do mesmo request ou de outro cliente que estava usando o sistema no mesmo segundo? Você tem log. Você não tem informação. São coisas diferentes. O problema não é a falta de log. É o excesso de log inútil. public function emitir ( Pedido $pedido ) { Log :: info ( 'entrou no emitir' ); try { $nota = $this -> sefaz -> emitir ( $pedido ); Log :: info ( 'emitiu' ); } catch ( Throwable $e ) { // parabéns, você registrou que algo deu errado em algum lugar 🎉 Log :: error ( 'Erro ao emitir nota' ); return back () -> withErrors ( 'Falha na emissão' ); } } Repara no que esse catch jogou no lixo: a mensagem da exceção, o stack trace, o ID do pedido, o CNPJ, o retorno da SEFAZ. Tudo estava ali, na mão, e foi substituído por uma frase genérica. E os Log::info('entrou') espalhados? Aquilo foi debug que virou permanente. Hoje eles só servem pra empurrar as linhas úteis pra fora da tela. Duas perguntas que todo log precisa responder Um log serve pra duas plateias: você, com sono, às 3h da manhã — e uma máquina , filtrando milhões de linhas. As duas querem a mesma coisa: O que aconteceu , numa mensagem que não muda nunca. Com quem aconteceu , em dados separados da mensagem. Essa separação é o pulo do gato. Repare na diferença: // ❌ mensagem única pra cada pedido. impossível agrupar ou contar. Log :: error ( "Falha ao emitir nota do pedido { $pedido -> id } do cliente { $cliente -> nome } " ); // ✅ mensagem estável + contexto est
AI 资讯
Middleware é porteiro, não gerente
Ele começou com um if . Hoje tem 80 linhas. Sabe como é: precisava barrar quem não tem assinatura ativa. Um middleware, três linhas, resolvido. Depois entrou o período de teste. Depois o plano legado que tem regra diferente. Depois "aproveita que já buscou a assinatura e desconta um crédito". Depois o e-mail de aviso quando faltam 3 dias pro vencimento. Hoje esse arquivo tem 80 linhas, faz quatro queries, altera dado no banco e dispara e-mail. Ele não é mais um middleware. É um Service que mora na pasta errada e roda em todo request. E o pior: essa regra não existe pro resto do seu sistema. O middleware que virou gerente class VerificarAssinatura { public function handle ( Request $request , Closure $next ): Response { $assinatura = $request -> user () -> assinatura ; if ( ! $assinatura || $assinatura -> venceu ()) { return redirect () -> route ( 'planos' ); } // "aproveita que já tá aqui" 🙃 if ( $assinatura -> creditos < 1 ) { return redirect () -> route ( 'planos' ) -> withErrors ( 'Sem créditos' ); } $assinatura -> decrement ( 'creditos' ); $assinatura -> update ([ 'ultimo_acesso' => now ()]); if ( $assinatura -> vence_em -> diffInDays ( now ()) <= 3 ) { Mail :: to ( $request -> user ()) -> send ( new AssinaturaVencendo ( $assinatura )); } return $next ( $request ); } } Funciona. Passa nos testes de feature. E tem quatro problemas escondidos que só aparecem meses depois. Problema 1: middleware só existe no HTTP Esse é o grande. Middleware é uma camada de request HTTP . Ela não roda em outro lugar nenhum. Então: O comando php artisan relatorio:gerar não desconta crédito. O job na fila não desconta crédito. Sua rota de API que você esqueceu de agrupar não desconta crédito. O tinker passa por cima de tudo. Você não criou uma regra de negócio. Criou uma regra da porta da frente . Qualquer outra entrada no sistema ignora ela. E, sério, isso não é hipótese: um dia alguém vai criar um endpoint novo, esquecer o middleware, e a assinatura vira um detalhe decorativo. Probl
AI 资讯
MCP Is Going Stateless: What Changed and How I Migrated My Currency Converter Server
The Model Context Protocol (MCP) has been evolving quickly. One of the most interesting changes in the latest MCP specification is the move toward a stateless protocol model . I recently updated my MCP currency converter server to work with the newer stateless behavior and the new split TypeScript SDK packages, particularly @modelcontextprotocol/server . In this article, I'll explain: What MCP sessions were doing What "stateless MCP" actually means Why the change matters for production systems How Streamable HTTP changes with the new specification How I migrated my currency converter MCP server What this means for scaling MCP servers What Is MCP? If you're new to MCP, the Model Context Protocol is a standard for connecting AI applications to external tools, resources, and data. Instead of building custom integrations between every AI application and every external service, MCP provides a common protocol. For example, an AI assistant can use an MCP server exposing a tool like: convert_currency The model can then request: Convert 100 USD to EUR. The MCP client communicates with the MCP server, which performs the actual operation and returns the result. MCP servers can expose several primitives, including tools, resources, and prompts. For my example, the server is intentionally simple: it exposes currency-conversion functionality. The Old Mental Model: MCP Sessions Before the stateless changes, Streamable HTTP could maintain a protocol-level session. Conceptually, the flow looked something like this: Client | | POST /mcp | initialize v MCP Server | | Mcp-Session-Id v Client | | POST /mcp | Mcp-Session-Id: abc123 v MCP Server The server creates a session during initialization. Subsequent requests contain the session identifier. That means the server can associate requests with the session that was established earlier. This isn't necessarily bad. Session state can be useful when an application genuinely needs conversational or connection-level state. But it creates an a
AI 资讯
Mastering Idempotent Consumers in MuleSoft for Seamless No-Code Integration Events
Unlock Seamless Idempotent Processing Without Coding Hurdles As a seasoned integration mentor, I'm here to walk you through a simple, no-code/low-code method to tackle the thorny issue of idempotent consumers in MuleSoft Anypoint. You’ve likely struggled with pre-built connectors and complex data transformations, but let’s take this one step at a time—no Java or XML required. The 3-Click Path: From Complexity to Simplicity Define Your Idempotency Key : Start by selecting the unique identifier in your message that will serve as your idempotency key. This could be an order ID, transaction number, or any field that uniquely identifies each event. Set Up Object Store Configuration : Navigate to MuleSoft’s Object Store configuration within Anypoint Studio and configure it for storing these keys. Here, you can choose between In-Memory or Persistent storage options depending on your scalability needs. Apply Idempotent Filter Component : Drag the “Idempotent Filter” component into your flow where you want to enforce idempotency. Configure this filter by specifying the object store and the key field that uniquely identifies each incoming event. And just like that, you’ve set up a system that ensures even when an integration event is delivered multiple times, it will only process once—eliminating double-charges or redundant data entries in your downstream systems. Why This Matters for Low-Level Beginners For many of us working with MuleSoft and similar platforms, the complexity around ensuring message processing integrity can seem daunting. Yet, by simplifying this process through intuitive component usage, we ensure that each event is processed exactly once, maintaining system accuracy without diving into complex scripting or configuration. Conclusion: Empowering Automators As you continue on your journey of automating data flows and enhancing business processes, remember—MuleSoft’s capabilities extend far beyond what rigid pre-built connectors might suggest. Embrace these n
AI 资讯
How Do I Send Password Reset Emails from a Backend App Using an Email API?
Here's the full flow the way I've built it, using Notify as the email API. The shape of this is the same regardless of which provider you pick — generate a token, send a link, verify it on submit — so most of this applies no matter what you're using; I'll flag the one part that's specific to Notify. The Flow, End to End User requests a password reset Your backend generates a secure, short-lived reset token Your backend stores a hashed version of that token Your backend sends an email with the reset link, through an email API User clicks the link and submits a new password Your backend verifies the token, updates the password, and invalidates the token Step 1: Generate the Reset Token Use a cryptographically secure random value, not anything guessable, and store only a hashed version in your database — if your database ever leaks, the raw tokens aren't exposed alongside it: const crypto = require ( ' crypto ' ); function generateResetToken () { const token = crypto . randomBytes ( 32 ). toString ( ' hex ' ); const tokenHash = crypto . createHash ( ' sha256 ' ). update ( token ). digest ( ' hex ' ); return { token , tokenHash }; } Give it a short expiration — 15 to 60 minutes is typical. Step 2: Build the Reset URL https://yourapp.com/reset-password?token=RESET_TOKEN The token goes in the link the user clicks; the hash is what you store and check against later. Step 3: Send the Email This is the Notify-specific part. There's no SDK to install — it's a single HTTP request with your API key in the header: async function requestPasswordReset ( email ) { const user = await findUserByEmail ( email ); // Don't reveal whether the email exists if ( ! user ) return ; const { token , tokenHash } = generateResetToken (); const expiresAt = new Date ( Date . now () + 1000 * 60 * 30 ); // 30 minutes await saveResetToken ( user . id , tokenHash , expiresAt ); const resetLink = `https://yourapp.com/reset-password?token= ${ token } ` ; await fetch ( ' https://notify.cx/api/email/send
AI 资讯
Build One Guarded Prisma Endpoint, Then Break It Five Ways
A generated route can remove repetitive Express handlers without removing the API contract. That distinction becomes concrete when one endpoint is deliberately broken in five small ways. Each break below changes either shape construction, request validation, emitted Prisma arguments, or execution-time projection. The status code alone is not enough to identify which layer moved. The examples use prisma-guard 1.33.0, Prisma 6.19.3, and Zod 4.4.3. Those versions are pinned because several observations concern exact runtime behavior. The goal is a test you can rerun during upgrades, not a rule inferred from one successful response. Start with a small tenant model. Nursery is the scope root, and Plant carries the foreign key that the guard extension can constrain. /// @scope-root model Nursery { id String @id @default(cuid()) name String plants Plant[] } model Plant { id String @id @default(cuid()) name String priceCents Int isPublished Boolean @default(false) nurseryId String nursery Nursery @relation(fields: [nurseryId], references: [id]) } The generated router still needs an extended Prisma client and trusted request context. Authentication remains application code. The important detail is that the tenant ID comes from the authenticated session, not from the query string or body. import { AsyncLocalStorage } from ' node:async_hooks ' import { PrismaClient } from ' @prisma/client ' import { guard } from ' ./generated/guard/client ' type RequestContext = { nurseryId : string ; audience : ' public ' | ' seller ' } const requestStore = new AsyncLocalStorage < RequestContext > () const prisma = new PrismaClient (). $extends ( guard . extension (() => { const context = requestStore . getStore () return { Nursery : context ?. nurseryId , caller : context ?. audience } }), ) Now define one public read contract. In a guard shape, true means the client may choose a value. A literal means the server chose it. force(true) is required to pin a Boolean to true because bare true is
AI 资讯
JWT Authentication in Express That You Can Actually Revoke
Access tokens, refresh token rotation, and theft detection: the parts most Node.js tutorials leave out. A friend messaged me about his side project a few months ago: "Someone else is logged into my account. I changed my password. They're still in." He had followed the tutorials to the letter. Sign a JWT on login, send it to the frontend, keep it in localStorage , attach it to every request. Done. What none of those tutorials mentioned is that this setup has no way to un -log anyone in. A JWT is a signed piece of paper. Once you hand it over, it stays valid until it expires, and his expired in 30 days. Changing the password accomplished nothing, because the token had already been signed and nothing about it depended on the password. There was no list of active sessions to delete from. There was nothing to revoke. His only remaining move was rotating the signing secret, which logged out every user on the platform at once. That was his entire kill switch: burn it all down. This is the walkthrough I wish someone had handed me the first time I built auth. Token design, storage, refresh rotation, theft detection, the Express code, the Axios interceptor on the frontend, and the specific mistakes that turn a working login into an incident. It's long. Auth is one of those areas where the missing ten percent is the part that gets you. What the standard tutorial leaves out Nearly every "JWT authentication in Node.js" post ends in the same place: sign a token, put it in localStorage , send a Bearer header. That gets you a demo. Four things stand between that and production. localStorage is readable by any JavaScript on the page. That includes the analytics snippet you added last week, the npm package that got compromised upstream, and any XSS hole in your own code. One call to localStorage.getItem('token') and an attacker holds a working credential they can replay from their own machine. You can't detect it and you can't stop it. There is no revocation. The appeal of JWTs is st
AI 资讯
Observability - A Counter in RAM, an ID in a Header, and a Batch Export
For a long time, my mental model of observability was this: you import an SDK, sprinkle some calls through your code, each call fires off data to a server somewhere, and a dashboard reads it back. A logging system with extra steps. That model is wrong in a specific, interesting way. And I couldn't see how it was wrong until I stopped looking at the dashboards and started looking at what actually gets emitted, and how. The seductive wrong model The wrong model is seductive because the plumbing really does look identical. Logging: emit, store, search. Observability: emit, store, query. Same loop, right? So my working theory became: observability is logging plus some fancy logic to analyze the logs. Close. But no. The difference isn't in the analysis. It's in the emission — and it splits into three mechanisms that have almost nothing in common with each other. Descent one: metrics aren't events at all A metric is not a record you write. It's a number sitting in your app's memory . requests_total . increment () // 1, 2, 3... request_duration . record ( 0.23 ) // adds to a histogram Nothing is sent when this line runs. The number just changes in RAM. Periodically — every 15 seconds, say — either a backend scrapes an endpoint your app exposes, or a collector ships the current values out. That's why metrics are absurdly cheap: a million requests is one counter reading "1,000,000", not a million records. You could never reconstruct a clean p99 latency graph by parsing log text. The histogram was built for it at write time. And the stateless-container objection answers itself: the in-memory counter is disposable. Each instance flushes to the backend on a schedule (on serverless, a sidecar collector even does a final flush at shutdown), and the backend sums across instances. The durable truth never lived in your app. Descent two: logs are the familiar part Logs work exactly the way I always assumed everything worked: an event, written out, shipped, searched. The only upgrade
AI 资讯
How to Integrate a Payment Gateway into Your Web App: A Practical Guide
Adding online payments to a web application can make it easier for customers to purchase products, subscribe to services, book appointments, or pay invoices. But payment integration involves more than adding a payment button to a website. A reliable integration needs a payment gateway, backend APIs, secure authentication, payment status handling, webhooks, and proper error management. This guide explains the basic process of integrating a payment gateway into a web application, using Razorpay as an example. 1. Understand How Payment Gateway Integration Works A typical payment flow looks like this: Customer → Web App → Backend → Payment Gateway → Bank/Payment Network The customer starts the payment from your website. Your backend creates the payment order through the gateway. The customer then completes the payment using a supported payment method. After the transaction, your application needs to confirm whether the payment was successful before providing the product or service. A simplified flow is: Customer selects a product or service. Your backend creates an order. The payment gateway generates the required payment details. Checkout opens for the customer. Customer completes the payment. The gateway returns payment information. Your backend verifies the payment. A webhook can update your system about payment events. Your database records the final payment status. The application confirms the order. 2. Choose the Right Payment Gateway Before starting development, compare payment gateways based on factors such as: Supported payment methods Transaction fees API documentation Developer tools Settlement process Refund support International payment support Webhook capabilities Security requirements Customer support For an Indian web application, gateways such as Razorpay can support common payment methods including UPI, cards, net banking, and wallets, depending on the account and applicable availability. The important thing is to choose a gateway that fits your applic
AI 资讯
Token Bucket vs. Sliding Window: Building Rate Limiters That Actually Hold Under Load
Rate limiting sounds like a solved problem until you actually implement one and watch it fail in a way your load test didn't predict: legitimate bursts getting rejected, or a limiter that lets through 2x its stated limit at window boundaries. The failure modes are specific enough that it's worth working through the two dominant algorithms — token bucket and sliding window — with actual code, not just the diagrams. The problem with fixed windows The naive approach almost everyone reaches for first is a fixed window counter: pick a window size (say, 60 seconds), count requests in that window, reset the counter when the window rolls over. import time class FixedWindowLimiter : def __init__ ( self , limit : int , window_seconds : int ): self . limit = limit self . window_seconds = window_seconds self . count = 0 self . window_start = time . time () def allow ( self ) -> bool : now = time . time () if now - self . window_start >= self . window_seconds : self . window_start = now self . count = 0 if self . count < self . limit : self . count += 1 return True return False This is simple and cheap, and it's also broken in a specific, exploitable way. Say the limit is 100 requests/minute. A client can send 100 requests in the last second of window N, then another 100 in the first second of window N+1. That's 200 requests in roughly two seconds, well within the letter of "100/minute" as the code enforces it, but nowhere near the spirit of it. This is the classic boundary-burst problem, and it's the reason fixed windows get replaced once traffic is adversarial or bursty enough to find the seam. Sliding window: smoothing the boundary A sliding window log fixes this by tracking actual timestamps instead of a single counter, and counting how many fall within the trailing window at the moment of the request: from collections import deque import time class SlidingWindowLogLimiter : def __init__ ( self , limit : int , window_seconds : float ): self . limit = limit self . window_seco
AI 资讯
UPI at Scale: Handling Millions of Payments
Imagine this: It's salary day. It's 2 PM. Millions of people across India suddenly open their UPI apps and start paying rent, sending money to family, paying credit-card bills, and shopping online. Now here's the system-design interview question: If millions of people make payments at almost exactly the same time, is every request hitting one central server? What prevents the entire payment system from freezing? At first glance, it sounds like a scaling problem. It isn't just a scaling problem. It's a combination of: horizontal scaling concurrency distributed systems database consistency retries idempotency backpressure failure isolation downstream bottlenecks And that's what makes payment systems such an interesting system-design problem. First: Don't Imagine One Giant UPI Server A common mental model looks like this: Millions of users | v +-------------+ | UPI Server | +-------------+ | v Bank If that were literally true, we'd have a pretty serious problem. One machine cannot safely process the country's entire payment traffic. Instead, think about a distributed system: Users | v +---------------+ | API / Gateway | +---------------+ / | \ / | \ v v v [S1] [S2] [S3] | | | +------+------+ | Payment Services | +--------+--------+ | | Bank A Bank B The exact implementation of a real payment network is much more complicated than this diagram, but this is the right system-design mental model . The important idea is: The system is distributed across many machines and participating institutions. Step 1: The First Problem — Traffic Spikes Let's take a concrete example. You want to pay your landlord: ₹25,000 At the same moment, millions of other people are doing something similar. Suddenly: Normal traffic: 100K requests/sec Salary day: ████████████████████████ 1M+ requests/sec The first question is: How do we handle the additional traffic? Naive Solution: One Powerful Server We could buy a massive machine. 1M requests/sec | v +---------------+ | HUGE SERVER | | 256 CPU core
AI 资讯
Building a Distributed System in Go: Part 1 — In-Process Message Passing & CSP Primitives
Welcome to Part 1 of the Go Distributed Systems Lab series! Over the course of 20 hands-on projects, we are building core distributed systems primitives from the ground up using Go 1.22+ and the standard library ( net , sync , context , log/slog , encoding/binary ). Before jumping into raw socket framing, gossip protocols, or Raft consensus, we need to master the foundational concurrency building blocks inside a single process: Goroutines, Channels, and Communicating Sequential Processes (CSP) . 💡 The Philosophy: Share Memory by Communicating In traditional concurrent programming (like C++ or Java), thread synchronization often relies on shared memory protected by mutexes, lock-free queues, or read-write locks. Go flips this model with a core design principle: "Do not communicate by sharing memory; instead, share memory by communicating." By passing ownership of data structures through Go channels, each pipeline stage operates on isolated memory. This eliminates data races by design without requiring explicit lock management ( sync.Mutex ). 🏗️ Architecture & Component Design In this first module ( 01-message-passing ), we construct a 3-stage data processing pipeline: +------------------+ Job Channel +------------------+ Result Channel +-------------------+ | Producer | -------------------------> | Worker | ------------------------> | Collector | | (Generates Jobs) | (Buffered, cap=10) | (Isolated State) | (Buffered, cap=10) | (Aggregates Data) | +------------------+ +------------------+ +-------------------+ 1. Ingestion Stage (Producer) Generates typed Job values and pushes them into a direction-constrained buffered channel ( chan<- Job ). When generation finishes, it closes the channel to broadcast an end-of-stream signal. 2. Processing Stage (Worker) Consumes from <-chan Job using Go's for job := range in construct. The worker maintains internal execution metrics (e.g., processedCount ) entirely within its local stack scope—no locks required. 3. Collector Stage R