<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ritesh Singh – Blog</title><link>https://hireritesh.com/blog/</link><atom:link href="https://hireritesh.com/blog/rss.xml" rel="self" type="application/rss+xml"/><description>Practical guides on web apps, APIs, fintech integrations and deployment.</description><language>en</language><item><title>One API for Your Flutter App and Web Dashboard: A Practical Guide</title><link>https://hireritesh.com/blog/one-api-for-flutter-app-and-web-dashboard/</link><guid>https://hireritesh.com/blog/one-api-for-flutter-app-and-web-dashboard/</guid><pubDate>Sun, 11 Oct 2026 06:00:00 +0000</pubDate><description>How to design one Node.js or PHP REST API that serves both a Flutter mobile app and a web dashboard: versioning, auth, errors, roles and push notifications.</description><content:encoded><![CDATA[<p>A very common product shape today is a web dashboard for the business and a mobile app for customers or field staff. Both need the same data: users, orders, cases, bookings, payments. The cleanest way to build this is one backend API that both clients use, instead of two separate systems that slowly drift apart. Most of the products I build follow this pattern, for example a <a href="https://hireritesh.com/projects/legal-case-management/">legal case management platform</a> where the Next.js web app and the mobile app share one Node.js API, and a <a href="https://hireritesh.com/projects/radioasia-web-radio/">web radio platform</a> whose JSON API also feeds the station's Flutter app.</p><p>Sharing an API sounds simple, but a mobile app puts constraints on the backend that a website alone never does. This guide covers the decisions that matter most when one REST API, written in Node.js/Express or PHP, has to serve a Flutter app and a web dashboard at the same time.</p><h2>Why one API instead of two</h2><p>With one API, business rules live in one place. A discount, a permission check or a status change is written once and behaves the same on the website and in the app. Bugs are fixed once. New clients, such as a second app, can be added later without copying logic. The cost is that the API has to be designed for its slowest-moving client, and that client is almost always the mobile app.</p><h2>1. Version the API from the first release</h2><p>You can redeploy a website in minutes, and every visitor gets the new version on their next page load. A mobile app is different. Store review takes time, and many users do not update for weeks or months. That means old app builds will keep calling your API long after you have changed it.</p><p>Put a version in the path from day one, such as <code>/api/v1/</code>. Add new fields freely, because older apps simply ignore them, but never rename or remove a field, or change its type, inside the same version. When a breaking change is unavoidable, add <code>/api/v2/</code> for the new behaviour and keep v1 running until usage drops.</p><p>It also helps to add a small endpoint that returns the minimum supported app version. The Flutter app checks it on start and shows an "Update required" screen when it is too old. This gives you a safe way to retire an old API version without leaving users with a broken app.</p><h2>2. Use authentication that suits both clients</h2><p>Browsers and mobile apps store credentials differently. A common approach that works well for both is a short-lived access token, such as a JWT valid for 15 to 60 minutes, plus a longer-lived refresh token that can be revoked on the server. On the web dashboard, keep tokens in HttpOnly cookies so page scripts cannot read them. In Flutter, store the refresh token in secure storage backed by the Android Keystore and iOS Keychain, for example with the <code>flutter_secure_storage</code> package, not in plain shared preferences.</p><p>Build the refresh flow into the app's HTTP client once: when a request returns 401, refresh the token and retry the request a single time. Keep a record of refresh tokens per device on the server, so a user can log out of a lost phone without being logged out everywhere else.</p><h2>3. Agree on one response and error format</h2><p>The fastest way to slow down a mobile team is inconsistent responses. Pick one shape for success and one for errors, and use it on every endpoint. A useful error response contains a stable machine-readable code, a human-readable message and, for form validation, the message for each field. The app can then highlight the right input without parsing English text.</p><p>A few small rules save a lot of debugging later. Send dates in ISO 8601 format in UTC and let each client format them for the user's time zone. Send money as integer amounts in the smallest unit, such as paise, or as strings, never as floating-point numbers. Use the correct HTTP status codes, so the Flutter client can tell a validation error (422) from an expired login (401) or a server problem (500).</p><h2>4. Keep mobile payloads small</h2><p>Dashboards on office broadband can afford large responses. An app on a weak mobile connection cannot. Paginate every list endpoint, and return only the fields a list screen needs, with a separate detail endpoint for the full record. Resize images on upload and serve thumbnails for lists. Enable gzip or Brotli compression on the server. These changes make the app feel faster without touching any Flutter code.</p><h2>5. Enforce roles and permissions in the API</h2><p>When a web dashboard and an app share a backend, they usually serve different roles: an owner or admin on the web, staff or customers on the phone. Hiding a button in the interface is not security. Every endpoint must check on the server who the user is and what they may see or change. In the legal platform, for example, advocates, office staff and clients see different data, and that rule is enforced by the API rather than by each screen.</p><h2>6. Plan push notifications and background work</h2><p>Mobile apps usually need push notifications: a new booking, a status change, a reminder for a hearing date. Store each device's push token, such as a Firebase Cloud Messaging token, against the user and the device, and remove tokens that the push service reports as invalid. Send notifications from a background job or queue rather than inside the API request, so a slow push service never slows down the user who triggered the change.</p><h2>7. Document the API and give the app a staging server</h2><p>Mobile developers should not have to read backend code to know what an endpoint returns. Keep endpoint documentation, for example an OpenAPI file or a shared Postman collection, up to date with every change. Run a staging copy of the API with test data, so app builds under review or in testing never touch production records. When the backend and both clients are deployed with containers, staging is cheap to run; my guide to <a href="https://hireritesh.com/blog/deploy-nextjs-with-coolify/">deploying a Next.js app and API with Coolify</a> shows one way to host both on a single VPS.</p><h2>A quick checklist before launch</h2><p>Before the first app release, check that every route is under a version prefix, that old app builds have a forced-update path, that tokens refresh and can be revoked per device, that every error uses the same format, that list endpoints are paginated, that permissions are enforced on the server, that push tokens are cleaned up, and that the API has documentation and a staging environment. None of this is complicated on its own, but adding it after launch is much harder than starting with it.</p><h2>Need one developer for the API and the app?</h2><p>Because I build the backend and the clients together, the API, the <a href="https://hireritesh.com/services/flutter-app-developer/">Flutter or React Native app</a> and the <a href="https://hireritesh.com/services/nextjs-developer/">Next.js dashboard</a> are designed as one product, with one person accountable for all of it. If you already have a backend, I can review it and connect a new app to it. See my <a href="https://hireritesh.com/services/nodejs-api-developer/">Node.js and PHP API development</a> service, or send me a short brief about your product.</p><hr><p><em>Originally published at <a href="https://hireritesh.com/blog/one-api-for-flutter-app-and-web-dashboard/">hireritesh.com</a>. Need help with a project like this? <a href="https://hireritesh.com/">Hire Ritesh Singh</a>, freelance full-stack developer.</em></p>]]></content:encoded></item><item><title>Add Agora Live Video to an Existing Website: Tokens, Roles and Reconnects</title><link>https://hireritesh.com/blog/add-agora-live-streaming-to-existing-website/</link><guid>https://hireritesh.com/blog/add-agora-live-streaming-to-existing-website/</guid><pubDate>Fri, 09 Oct 2026 06:00:00 +0000</pubDate><description>How to add Agora RTC live streaming to a PHP or Node.js website: secure tokens, host and audience roles, reconnect handling and a launch checklist.</description><content:encoded><![CDATA[<p>Many businesses already have a working website or platform and want to add live video: a coaching session, a product launch, a live class or an event broadcast. Rebuilding the whole product is rarely necessary. With the Agora RTC SDK you can add a live streaming module to an existing PHP or Node.js site, as long as you get a few important pieces right. This guide covers the parts that usually decide whether the feature works smoothly in production.</p><h2>How the pieces fit together</h2><p>An Agora live stream has three parts. Your <strong>server</strong> decides who may join which session and issues a short-lived token. The <strong>browser or app</strong> uses the Agora SDK to join a channel with that token, then publishes or plays audio and video. Agora's <strong>network</strong> carries the media between hosts and viewers. Your own server never handles the video itself, so you do not need streaming servers or extra bandwidth on your hosting plan.</p><p>This split is why live video fits well into an existing platform. Your current login, database and pages stay as they are. You add one endpoint that returns a token, and one page or component that runs the player.</p><h2>1. Never expose the App Certificate</h2><p>In the Agora console every project has an App ID and an App Certificate. The App ID is public and goes into your frontend code. The App Certificate is a secret: it is used to sign tokens and must only live on your server, in an environment variable, never in JavaScript or a mobile app build.</p><p>Enable the certificate for any project that goes to production. Testing mode without tokens is convenient for a first demo, but it means anyone who finds your App ID can join your channels.</p><h2>2. Build a small token endpoint</h2><p>Agora publishes official token builder libraries for several languages. In Node.js the <code>agora-token</code> package provides <code>RtcTokenBuilder</code>; for PHP you can use the token builder classes from Agora's open-source <code>AgoraDynamicKey</code> repository. Each token is built from the App ID, the App Certificate, a channel name, a user ID, a role and an expiry time.</p><p>The endpoint should do more than sign a token. Before it returns anything, check that the user is logged in, that the session exists and is currently live or about to start, and that this user is allowed to watch it, for example because they bought the class or belong to the right organisation. Only then return the token. This is where your existing business rules protect the stream.</p><p>Use a channel name your server controls, such as the session ID from your database, rather than anything typed by the user. Map your own user IDs to the numeric UID you pass to Agora so you can tell later who joined.</p><h2>3. Give hosts and viewers different roles</h2><p>For broadcasts, create the client in <code>live</code> mode. Hosts join with the host role and publish their camera and microphone. Viewers join with the audience role and only subscribe. Issue the publisher role in the token only to users your server has confirmed as hosts; everyone else gets a subscriber token. That way a viewer cannot start publishing just by changing code in the browser.</p><p>If you need a viewer to come on stage, for a question or a co-host segment, request a new token with the publisher role from your server and switch the client role. Keep that decision on the server side too.</p><h2>4. Handle token expiry and reconnects</h2><p>Tokens expire, and long sessions often run past the first token's lifetime. The Web SDK raises a <code>token-privilege-will-expire</code> event shortly before expiry. Listen for it, fetch a fresh token from your endpoint and call <code>renewToken</code>. If you skip this, users are silently dropped from a session that is still running, which is one of the most common complaints after launch.</p><p>Networks also drop, especially on mobile data. Listen to the <code>connection-state-change</code> event and show a clear message such as "Reconnecting…" instead of a frozen video. The SDK retries automatically in many cases, but the viewer needs to know what is happening, and you need a fallback, such as a "Rejoin" button, if the connection does not recover.</p><h2>5. Design the waiting and empty states</h2><p>A live page spends a lot of its time not live. Plan what viewers see before the host arrives, when the host's camera is off, when the host leaves, and after the session ends. Subscribe to remote users when they publish, and remove their video tile when they unpublish or leave. These small states make the difference between a feature that feels finished and one that looks broken.</p><p>Also handle permissions properly. Browsers ask hosts for camera and microphone access, and the request can be denied or the device can already be in use by another app. Catch those errors and explain the fix in plain language.</p><h2>6. Check these before launch</h2><p>Test with real devices on different networks, not just two browser tabs on office Wi-Fi. Confirm the page is served over HTTPS, because browsers block camera access on insecure pages. Test a session longer than your token lifetime. Try joining with an expired or invalid token to make sure it is rejected. Check what happens when the host refreshes the page mid-session. Finally, log join, leave and error events on your server so you can answer support questions with facts.</p><h2>Mobile apps use the same backend</h2><p>If you also have a Flutter or React Native app, it can use the same token endpoint and channel names as the website. Agora provides SDKs for both, so web and mobile viewers can join the same live session. Designing the endpoint well once saves you from maintaining two separate permission systems.</p><p>Want live video inside your current platform without rebuilding it? I integrate Agora into existing PHP and Node.js products, including the token server, the host and viewer pages, and the mobile app API. Send me a short brief and I will suggest the simplest way to add it.</p><hr><p><em>Originally published at <a href="https://hireritesh.com/blog/add-agora-live-streaming-to-existing-website/">hireritesh.com</a>. Need help with a project like this? <a href="https://hireritesh.com/">Hire Ritesh Singh</a>, freelance full-stack developer.</em></p>]]></content:encoded></item><item><title>How to Deploy a Next.js App and API with Coolify on Your Own VPS</title><link>https://hireritesh.com/blog/deploy-nextjs-with-coolify/</link><guid>https://hireritesh.com/blog/deploy-nextjs-with-coolify/</guid><pubDate>Thu, 08 Oct 2026 06:00:00 +0000</pubDate><description>A practical guide to deploying a Next.js frontend and a Node.js API with Coolify, Docker and Cloudflare on a single VPS.</description><content:encoded><![CDATA[<h2>Why self-host with Coolify</h2><p>Managed platforms are convenient, but costs climb as soon as you add a backend, a database and several environments. Coolify gives you push-to-deploy on a VPS you control, using Docker under the hood.</p><h2>1. Prepare the server</h2><p>Start with a fresh Ubuntu VPS with at least 2 GB of RAM. Point a domain at it through Cloudflare, and keep the proxy off (grey cloud) until SSL is issued.</p><h2>2. Install Coolify</h2><p>Install Coolify with its official install script, open the dashboard on port 8000, create your admin account, and connect your GitHub account or add a deploy key for private repositories.</p><h2>3. Deploy the Next.js frontend</h2><p>Create a new resource from your repository and choose the Nixpacks or Dockerfile build pack. Set the port to 3000, add environment variables such as NEXT_PUBLIC_API_URL, and attach your domain. Coolify requests the SSL certificate for you.</p><h2>4. Deploy the API as a separate service</h2><p>Keep the backend in its own repository or folder and deploy it as a second resource on an api subdomain. Add your database credentials as environment variables, never in the code.</p><h2>5. Wildcard subdomains for multi-tenant apps</h2><p>If every customer needs their own subdomain, add a wildcard DNS record in Cloudflare and configure the wildcard domain on the frontend resource. Read the subdomain in your app to load the right tenant.</p><h2>Common problems</h2><p>Builds failing on memory usually mean the VPS is too small or needs swap. A 502 after deploy usually means the app listens on a different port than the one configured. SSL errors usually mean the Cloudflare proxy was on before the certificate was issued.</p><p>Need this set up for your product? I do this regularly for clients and can have your app live on your own server.</p><hr><p><em>Originally published at <a href="https://hireritesh.com/blog/deploy-nextjs-with-coolify/">hireritesh.com</a>. Need help with a project like this? <a href="https://hireritesh.com/">Hire Ritesh Singh</a>, freelance full-stack developer.</em></p>]]></content:encoded></item><item><title>Bank API Integration Checklist: AES-256-GCM Encryption and HMAC Signing</title><link>https://hireritesh.com/blog/bank-api-integration-aes-gcm-hmac/</link><guid>https://hireritesh.com/blog/bank-api-integration-aes-gcm-hmac/</guid><pubDate>Thu, 08 Oct 2026 06:00:00 +0000</pubDate><description>What to check when integrating a bank or AEPS API that uses AES-256-GCM encrypted payloads and HMAC-SHA256 request signing.</description><content:encoded><![CDATA[<h2>Why bank integrations are different</h2><p>Bank APIs rarely accept plain JSON. Requests are usually encrypted, signed and tied to a timestamp, and responses come back encrypted too. A single wrong byte gives you a generic error with no hint about what went wrong.</p><h2>1. Confirm the exact key format</h2><p>Check whether the key is shared as raw bytes, hex or Base64, and decode it to exactly 32 bytes for AES-256. Many failures start with a key that was used as a text string instead of decoded bytes.</p><h2>2. Get the IV and tag layout right</h2><p>AES-GCM needs an IV (often 12 bytes) and produces an authentication tag (usually 16 bytes). Confirm with the bank how they expect these packed: IV first, tag at the end, everything Base64-encoded together, or sent as separate fields.</p><h2>3. Sign exactly what the bank signs</h2><p>HMAC-SHA256 has to be calculated over the exact string the bank expects: sometimes the encrypted payload, sometimes the plain JSON, sometimes fields joined in a fixed order. Match whitespace, field order and encoding character for character.</p><h2>4. Log safely in UAT</h2><p>Log the plaintext request, the encrypted payload, the signature and the raw response in UAT so you can compare them with the bank's sample values. Remove sensitive logs before production.</p><h2>5. Plan for status enquiry</h2><p>Payouts and beneficiary requests are not always final immediately. Build an enquiry job that checks pending records and updates their status, so your records always match the bank's.</p><p>Working on a bank or AEPS integration that keeps returning errors? I have debugged these in UAT and can help you get to production.</p><hr><p><em>Originally published at <a href="https://hireritesh.com/blog/bank-api-integration-aes-gcm-hmac/">hireritesh.com</a>. Need help with a project like this? <a href="https://hireritesh.com/">Hire Ritesh Singh</a>, freelance full-stack developer.</em></p>]]></content:encoded></item></channel></rss>
