Edward Hernandez

How to Build a REST API with Node.js and Express: A Complete Beginner’s Tutorial

How to Build a REST API with Node.js and Express: A Hands-On Task Manager Tutorial If you are wondering how to build a REST API with Node.js, this guide will walk you through the entire process by building something real: a simple task manager API. No abstract theory, no filler. By the end, you will have a working API with routes, a database, and tested endpoints in Postman. This tutorial is written for beginners with basic JavaScript knowledge. We will use the latest stable versions of Node.js and Express available in 2026. (via https://blog.postman.com) What You Will Build A REST API for a task manager that supports the following operations: Create a new task Retrieve all tasks Retrieve a single task by ID Update an existing task Delete a task We will map these actions to standard HTTP methods: Action HTTP Method Endpoint Create task POST /api/tasks Get all tasks GET /api/tasks Get one task GET /api/tasks/:id Update task PUT /api/tasks/:id Delete task DELETE /api/tasks/:id Prerequisites Node.js 22 LTS or later installed (check with node -v) A code editor such as VS Code Postman installed for testing endpoints A free MongoDB Atlas account (or a local MongoDB instance) Step 1: Scaffold the Node.js Project Create a new folder and initialize a Node.js project: mkdir task-manager-api cd task-manager-api npm init -y Open the generated package.json and add “type”: “module” so we can use modern ES module syntax. Step 2: Install Dependencies We only need a few packages to get started: npm install express mongoose dotenv npm install –save-dev nodemon Here is what each package does: express: the web framework for routing and middleware mongoose: an ODM to interact with MongoDB dotenv: loads environment variables from a .env file nodemon: restarts the server automatically during development Add these scripts to your package.json: “scripts”: { “start”: “node server.js”, “dev”: “nodemon server.js” } Step 3: Create the Server Entry Point Create a file called server.js in the root of your project: import express from ‘express’; import mongoose from ‘mongoose’; import dotenv from ‘dotenv’; import taskRoutes from ‘./routes/tasks.js’; dotenv.config(); const app = express(); app.use(express.json()); app.use(‘/api/tasks’, taskRoutes); app.get(‘/’, (req, res) => { res.json({ message: ‘Task Manager API is running’ }); }); const PORT = process.env.PORT || 3000; mongoose.connect(process.env.MONGO_URI) .then(() => { app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); }) .catch(err => console.error(‘Database connection failed:’, err)); Step 4: Configure Environment Variables Create a .env file at the project root: PORT=3000 MONGO_URI=mongodb+srv://YOUR_USER:[email protected]/taskmanager Never commit this file. Add .env to your .gitignore. Step 5: Define the Task Model Create a folder called models and inside it a file called Task.js: import mongoose from ‘mongoose’; const taskSchema = new mongoose.Schema({ title: { type: String, required: true, trim: true }, description: { type: String, default: ” }, completed: { type: Boolean, default: false } }, { timestamps: true }); export default mongoose.model(‘Task’, taskSchema); Step 6: Build the Routes Create a folder called routes with a file called tasks.js: import express from ‘express’; import Task from ‘../models/Task.js’; const router = express.Router(); // Create a task router.post(‘/’, async (req, res) => { try { const task = await Task.create(req.body); res.status(201).json(task); } catch (err) { res.status(400).json({ error: err.message }); } }); // Get all tasks router.get(‘/’, async (req, res) => { const tasks = await Task.find().sort({ createdAt: -1 }); res.json(tasks); }); // Get one task router.get(‘/:id’, async (req, res) => { try { const task = await Task.findById(req.params.id); if (!task) return res.status(404).json({ error: ‘Task not found’ }); res.json(task); } catch (err) { res.status(400).json({ error: ‘Invalid ID’ }); } }); // Update a task router.put(‘/:id’, async (req, res) => { try { const task = await Task.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true }); if (!task) return res.status(404).json({ error: ‘Task not found’ }); res.json(task); } catch (err) { res.status(400).json({ error: err.message }); } }); // Delete a task router.delete(‘/:id’, async (req, res) => { try { const task = await Task.findByIdAndDelete(req.params.id); if (!task) return res.status(404).json({ error: ‘Task not found’ }); res.json({ message: ‘Task deleted’ }); } catch (err) { res.status(400).json({ error: ‘Invalid ID’ }); } }); export default router; Step 7: Start the Server Run the development server: npm run dev If everything is configured correctly, you should see Server running on port 3000 in your terminal. There’s a good explainer over at dev.to. Step 8: Test Your Endpoints with Postman Open Postman and try the following requests one by one. Create a task (POST) URL: http://localhost:3000/api/tasks Body (raw JSON): { “title”: “Write blog post”, “description”: “Publish the Node.js tutorial on coding4.net” } Get all tasks (GET) Send a GET request to http://localhost:3000/api/tasks and you should see an array containing the task you just created. Update a task (PUT) Copy the _id from the response and send: PUT http://localhost:3000/api/tasks/THE_ID_HERE { “completed”: true } Delete a task (DELETE) Send a DELETE request to http://localhost:3000/api/tasks/THE_ID_HERE. You should receive a confirmation message. Step 9: Add Basic Error Handling Middleware Robust APIs handle unexpected errors gracefully. Add this at the bottom of server.js, just before mongoose.connect: app.use((req, res) => { res.status(404).json({ error: ‘Route not found’ }); }); app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: ‘Internal server error’ }); }); Best Practices to Keep in Mind Use versioning in URLs, for example /api/v1/tasks, so future changes do not break clients Validate input with a library like Zod or Joi before hitting the database Return proper HTTP status codes (201 for created, 404 for not found, 400 for bad request) Never store secrets in the codebase, always use environment variables Add authentication with JWT once your API grows past a basic prototype Enable CORS if a frontend from another domain will consume the API Project Structure Recap task-manager-api/ ├── models/ │ └── Task.js ├── routes/ │ └── tasks.js ├── .env ├── .gitignore ├── package.json └── server.js Next Steps You now have a working REST API. Here are ideas to push it further: Add user authentication with JWT tokens Deploy it to Render, Railway, or Fly.io Write automated tests with Vitest or Jest Document the endpoints with Swagger

How to Build a REST API with Node.js and Express: A Complete Beginner’s Tutorial Read More »

How to Prevent XSS Attacks in JavaScript: 6 Techniques With Code Examples

Cross-site scripting (XSS) remains one of the most exploited vulnerabilities on the web, and JavaScript apps are still the primary attack surface in 2026. If you’re building modern SPAs, Node.js APIs, or hybrid apps, learning how to prevent XSS attacks in JavaScript is not optional, it’s a core engineering skill. This guide skips the theory and gives you 6 actionable defense techniques you can ship today, each with working code examples. It is argued more carefully on mozilla.org. What Is an XSS Attack in JavaScript? An XSS attack happens when an attacker injects malicious JavaScript into your web application, which is then executed in the browser of another user. The result can be session hijacking, credential theft, defacement, or full account takeover. There are three main types you should recognize: Type How it works Typical vector Stored XSS Malicious script saved on the server and served to users Comments, profiles, forum posts Reflected XSS Script reflected from URL or form back into the page Search fields, error pages DOM-based XSS Vulnerability lives entirely in client-side JS innerHTML, location.hash, document.write Now let’s fix them. 1. Never Use innerHTML with Untrusted Data The single most common source of DOM-based XSS is innerHTML. If you insert user input directly, you hand attackers the keys. Vulnerable code: const username = new URLSearchParams(location.search).get(‘name’); document.getElementById(‘greeting’).innerHTML = ‘Hello ‘ + username; // URL: ?name=<img src=x onerror=alert(1)> → executes Safe alternative using textContent: const username = new URLSearchParams(location.search).get(‘name’); document.getElementById(‘greeting’).textContent = ‘Hello ‘ + username; // The <img> tag is rendered as literal text, not HTML Rule of thumb: Use textContent when you want text Use setAttribute when you want attributes Use createElement + appendChild for structured DOM Reserve innerHTML for static, developer-controlled strings only 2. Sanitize HTML with DOMPurify When You Really Need Rich Content Sometimes you actually need to render user-submitted HTML (rich text editors, markdown, emails). Don’t roll your own sanitizer. Use DOMPurify, the de-facto standard. import DOMPurify from ‘dompurify’; const dirty = userSubmittedHtml; // e.g. from a WYSIWYG editor const clean = DOMPurify.sanitize(dirty, { ALLOWED_TAGS: [‘b’, ‘i’, ’em’, ‘strong’, ‘a’, ‘p’, ‘ul’, ‘li’, ‘br’], ALLOWED_ATTR: [‘href’, ‘title’] }); document.getElementById(‘post’).innerHTML = clean; DOMPurify strips <script> tags, on* event handlers, javascript: URLs, and dozens of other bypass tricks that a homemade regex will miss. Don’t try to sanitize with regex Every year a new developer writes something like input.replace(/<script>/gi, ”) and every year attackers bypass it with <ScRipT>, <svg onload>, or encoded payloads. Just use a maintained library. 3. Encode Output Based on Context The same string is dangerous in different ways depending on where you inject it. Encode it for its context: Context Encoding required HTML body & < > ” ‘ → HTML entities HTML attribute HTML entity + always quote the attribute URL parameter encodeURIComponent() Inline JavaScript JSON.stringify() then HTML-encode CSS value Whitelist-based, avoid entirely if possible Example of a simple HTML encoder: function escapeHtml(str) { return String(str) .replace(/&/g, ‘&amp;’) .replace(/</g, ‘&lt;’) .replace(/>/g, ‘&gt;’) .replace(/”/g, ‘&quot;’) .replace(/’/g, ‘&#39;’); } 4. Deploy a Strict Content Security Policy (CSP) A well-configured CSP is your last line of defense. Even if an attacker sneaks a payload through, CSP can block execution. Send this header from your server: Content-Security-Policy: default-src ‘self’; script-src ‘self’ ‘nonce-r4nd0m123’; object-src ‘none’; base-uri ‘self’; frame-ancestors ‘none’; Then reference scripts with the matching nonce: <script nonce=”r4nd0m123″> // your legitimate inline script </script> Key rules for a strong CSP in 2026: Avoid ‘unsafe-inline’ and ‘unsafe-eval’ Use nonces or hashes instead of allowlisting domains where possible Always set object-src ‘none’ Add base-uri ‘self’ to prevent base tag injection Report violations with report-to before enforcing 5. Enable Trusted Types for DOM XSS Elimination Trusted Types is now widely supported in Chromium and Firefox browsers, and it’s the most powerful browser-level defense against DOM XSS. It forces every dangerous sink (innerHTML, script.src, etc.) to accept only vetted, policy-approved values. Enable it via CSP: Content-Security-Policy: require-trusted-types-for ‘script’; trusted-types default dompurify; Then define a policy in your app: import DOMPurify from ‘dompurify’; if (window.trustedTypes && trustedTypes.createPolicy) { trustedTypes.createPolicy(‘default’, { createHTML: (input) => DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: true }) }); } // Now this is automatically sanitized: element.innerHTML = untrustedInput; Any code that tries to assign a raw string to innerHTML without going through the policy will throw a TypeError. This is game-changing for large codebases. 6. Use Framework-Native Escaping Correctly Modern frameworks escape by default, but they all have escape hatches that are XSS landmines. React // Safe: automatically escaped <div>{userInput}</div> // DANGEROUS: bypasses escaping <div dangerouslySetInnerHTML={{ __html: userInput }} /> // Safer version if you must render HTML: <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} /> Vue <!– Safe –> <div>{{ userInput }}</div> <!– DANGEROUS –> <div v-html=”userInput”></div> Angular Angular auto-sanitizes bound HTML. Never call bypassSecurityTrustHtml() on untrusted input. Bonus: Extra Defensive Measures HttpOnly cookies: prevent JavaScript from reading session tokens SameSite=Strict or Lax: reduces impact of stolen sessions Secure flag: cookies transmitted only over HTTPS Subresource Integrity (SRI): verify third-party scripts haven’t been tampered with X-Content-Type-Options: nosniff: prevents MIME confusion attacks Regular dependency audits: run npm audit or Snyk in CI Quick XSS Prevention Checklist Replace every innerHTML with textContent where possible Sanitize all rich HTML input with DOMPurify Context-aware output encoding on both client and server Deploy a strict, nonce-based CSP Enable Trusted Types in supported browsers Never trust framework escape hatches with user data Set HttpOnly, Secure, SameSite on all cookies Audit dependencies regularly FAQ What is the best protection against XSS in JavaScript? There is no single silver bullet. The best protection combines context-aware output encoding, HTML sanitization with a library like DOMPurify, a strict Content Security Policy with nonces, and Trusted Types to eliminate DOM XSS sinks entirely. Is XSS still relevant in 2026? Yes. XSS remains in the OWASP Top 10 and continues to be one of the most reported web vulnerabilities. Modern SPAs, third-party widgets, and browser extensions all introduce new attack surfaces every year. Does React fully protect me from XSS? No. React escapes text nodes and attributes by default, but dangerouslySetInnerHTML, href with

How to Prevent XSS Attacks in JavaScript: 6 Techniques With Code Examples Read More »

How to Use CSS Flexbox: A Beginner’s Guide With 12 Practical Examples

If you have ever spent hours trying to center a div or align items in a row, this CSS Flexbox tutorial is for you. Instead of drowning you in theory, we are going to build 12 real layouts you can copy, paste and tweak. By the end of this guide, you will be comfortable creating navigation bars, card grids and responsive designs using nothing but Flexbox. What is CSS Flexbox in Simple Terms? Flexbox (Flexible Box Layout) is a one-dimensional layout system for arranging items in a row or a column. It lets a container distribute space between its children automatically, even when their size is unknown or dynamic. You only need to know two concepts to get started: Flex container: the parent element with display: flex. Flex items: the direct children of that container. Setting Up Your First Flex Container Before jumping into examples, here is the base HTML we will reuse throughout this tutorial: <div class=”container”> <div class=”item”>1</div> <div class=”item”>2</div> <div class=”item”>3</div> </div> And the minimal CSS to activate Flexbox: .container { display: flex; } That single line already places your items in a row. Now let’s build real things. Example 1: Horizontal Navigation Bar The classic use case. Items sit in a row with spacing between them. .navbar { display: flex; gap: 20px; padding: 15px; background: #222; } .navbar a { color: white; text-decoration: none; } Example 2: Centering Anything (Vertically and Horizontally) The famous centering problem, solved in 3 lines: .center { display: flex; justify-content: center; align-items: center; height: 100vh; } justify-content handles the main axis (horizontal by default). align-items handles the cross axis (vertical by default). Example 3: Push One Item to the Right Perfect for a logo on the left and a login button on the right. .header { display: flex; } .login-btn { margin-left: auto; } Example 4: Equal Width Columns with flex-grow Want three columns that share the space equally regardless of content? .item { flex: 1; } The flex: 1 shorthand means each item grows to fill available space equally. This is one of the most useful lines in modern CSS. Example 5: Sidebar + Main Content Layout .layout { display: flex; } .sidebar { flex: 0 0 250px; } .main { flex: 1; } The sidebar keeps a fixed 250px width, the main content takes the rest. Example 6: Card Grid with flex-wrap When you want items to wrap to the next line if they do not fit: .cards { display: flex; flex-wrap: wrap; gap: 20px; } .card { flex: 1 1 300px; } Each card will try to be 300px wide, grow if there is space, and wrap to a new row when needed. Example 7: Vertical Stack (Column Direction) .stack { display: flex; flex-direction: column; gap: 10px; } When flex-direction is set to column, the main axis becomes vertical. That means justify-content now controls vertical alignment. Implementing this cleanly is bread-and-butter for a capable development team. Example 8: Sticky Footer Keep the footer at the bottom even when content is short: body { display: flex; flex-direction: column; min-height: 100vh; } main { flex: 1; } Example 9: Space Between, Around and Evenly Here is a quick reference for the most useful justify-content values: Value Behavior flex-start Items packed at the start flex-end Items packed at the end center Items centered space-between First and last item at edges, equal gaps between space-around Equal space around each item space-evenly Equal space between and at edges Example 10: Reordering Items Without Touching HTML The order property lets you rearrange items visually: .item:nth-child(1) { order: 3; } .item:nth-child(2) { order: 1; } .item:nth-child(3) { order: 2; } Very handy for responsive designs where the mobile order differs from the desktop order. Example 11: Different Alignment for a Single Item Use align-self to override align-items for one specific item: .container { display: flex; align-items: flex-start; height: 200px; } .special { align-self: center; } Example 12: Fully Responsive Layout Without Media Queries Combining everything we learned: .responsive { display: flex; flex-wrap: wrap; gap: 16px; } .responsive > * { flex: 1 1 250px; } On large screens, you get multiple columns. On mobile, items stack automatically. No @media queries required. Understanding the Flex Shorthand The flex property is a shorthand for three values: flex-grow: how much the item grows relative to others flex-shrink: how much it shrinks when space is tight flex-basis: the initial size before growing or shrinking So flex: 1 1 200px means: grow equally, shrink equally, start at 200px. Flexbox vs CSS Grid: When to Use Which? Use Flexbox when… Use Grid when… You have a single row or column You need rows AND columns Content size drives the layout Layout drives content placement Navbars, toolbars, card lists Full page layouts, dashboards They are not competitors. Most modern sites use both together. Common Mistakes to Avoid Forgetting that flex properties apply to direct children only. Using height: 100% instead of letting Flexbox handle sizing. Mixing up main axis and cross axis after changing flex-direction. Using margins for spacing instead of the modern gap property. FAQ Is Flexbox hard to learn? Not at all. Once you understand the container/item relationship and the main/cross axis concept, you can build 90% of common layouts within a few hours of practice. Is Flexbox still relevant in 2026? Absolutely. Flexbox is supported in every modern browser and remains the standard for one-dimensional layouts. It works perfectly alongside CSS Grid and container queries. It is argued more carefully on mozilla.org. What is the difference between justify-content and align-items? justify-content aligns items along the main axis (horizontal by default). align-items aligns them along the cross axis (vertical by default). If you switch to flex-direction: column, their roles swap. Can I use Flexbox for a full page layout? Yes, but for two-dimensional layouts (rows and columns together), CSS Grid is usually cleaner. Many developers use Grid for the overall page structure and Flexbox inside components. What does flex: 1 actually do? It is a shorthand for flex: 1

How to Use CSS Flexbox: A Beginner’s Guide With 12 Practical Examples Read More »

How to Set Up a Git Branching Strategy for Small Teams: GitFlow vs Trunk-Based Development

If you work in a small dev team (2 to 10 people), your git branching strategy can either accelerate delivery or become a daily bottleneck. Too much process kills momentum. Too little creates chaos on main. This guide compares GitFlow and trunk-based development, gives you concrete naming conventions, merge workflows, and real command examples so you can pick what fits your release cadence without overengineering. Why Your Branching Strategy Matters (Even in a 3-Person Team) A branching strategy is a shared contract. It defines where code lives, how it gets reviewed, and when it ships. Small teams often skip this conversation and end up with: Long-lived feature branches that drift from main Painful merge conflicts on Friday afternoons Unclear release process (“what’s actually in production?”) Hotfixes applied directly to production with no traceability Pick a strategy early, document it in your CONTRIBUTING.md, and revisit it every 6 months. The Two Realistic Options for Small Teams Forget the 6-branch diagrams you saw on LinkedIn. For a small team, it comes down to two workable models: 1. GitFlow (structured, release-oriented) Introduced by Vincent Driessen, GitFlow uses multiple long-lived branches: main reflects production develop is the integration branch feature/* branches off develop release/* stabilizes a version before shipping hotfix/* patches production directly Best when: you ship on a fixed schedule (weekly, monthly), you support multiple versions in production, or you have QA gates. 2. Trunk-Based Development (fast, continuous) Everyone commits to main (the trunk) via short-lived branches that live less than 2 days. Feature flags hide unfinished work. Releases are cut from main as tags. There’s a good explainer over at dev.to. Best when: you deploy multiple times per week or per day, you have solid CI, and you trust your test suite. Side-by-Side Comparison Criteria GitFlow Trunk-Based Release cadence Scheduled (weekly/monthly) Continuous (daily+) Branch lifespan Days to weeks Hours to 2 days max Merge conflicts Frequent, larger Rare, small CI/CD required Nice to have Mandatory Feature flags Optional Highly recommended Learning curve Higher Lower Fits SaaS web apps Overkill Ideal Fits mobile / desktop / embedded Ideal Possible with tags Branch Naming Conventions That Actually Scale Whichever strategy you pick, enforce a naming convention. It makes filtering, automation, and code review much easier. Recommended pattern <type>/<ticket-id>-<short-description> feature/PROJ-142-add-oauth-login bugfix/PROJ-198-fix-null-user-avatar hotfix/PROJ-210-stripe-webhook-500 chore/upgrade-node-22 refactor/extract-payment-service Rules Lowercase only, dashes as separators Always prefix with the branch type Include the ticket ID (Jira, Linear, GitHub Issues) for traceability Keep it under 50 characters Delete the branch after merge (enable auto-delete in GitHub/GitLab) GitFlow in Practice: Real Commands Starting a feature git checkout develop git pull origin develop git checkout -b feature/PROJ-142-add-oauth-login # work, commit, push git push -u origin feature/PROJ-142-add-oauth-login Opening the PR and merging Open a Pull Request from feature/PROJ-142-add-oauth-login into develop. After review and green CI: git checkout develop git pull origin develop git merge –no-ff feature/PROJ-142-add-oauth-login git push origin develop git branch -d feature/PROJ-142-add-oauth-login Cutting a release git checkout develop git checkout -b release/1.4.0 # bump version, update changelog, fix last minute bugs git checkout main git merge –no-ff release/1.4.0 git tag -a v1.4.0 -m “Release 1.4.0” git push origin main –tags git checkout develop git merge –no-ff release/1.4.0 git push origin develop Emergency hotfix git checkout main git checkout -b hotfix/PROJ-210-stripe-webhook-500 # fix, commit git checkout main git merge –no-ff hotfix/PROJ-210-stripe-webhook-500 git tag -a v1.4.1 -m “Hotfix 1.4.1” git push origin main –tags git checkout develop git merge –no-ff hotfix/PROJ-210-stripe-webhook-500 git push origin develop Trunk-Based Development in Practice The core loop Pull latest main Create a short-lived branch Commit small and often Open a PR the same day Merge (squash) into main after review and CI Delete the branch Commands git checkout main git pull –rebase origin main git checkout -b feature/PROJ-142-oauth # small commits git push -u origin feature/PROJ-142-oauth # open PR, get review, CI green # merge via GitHub “Squash and merge” git checkout main git pull –rebase origin main Releasing Tag any commit on main that passes production checks: git checkout main git pull git tag -a v2026.07.28 -m “Production release” git push origin –tags Feature flags: your safety net Merge unfinished code behind a flag so main stays deployable: There’s a fuller breakdown if you want the detail. if (featureFlags.isEnabled(“new-checkout”)) { renderNewCheckout(); } else { renderLegacyCheckout(); } Tools like Unleash, LaunchDarkly, or a simple env-based flag work perfectly. How to Choose: A Decision Framework Ask yourself these five questions: How often do we deploy? More than 2x/week means trunk-based. Do we maintain multiple versions in production? Yes means GitFlow. Is our CI reliable (tests, linting, security scan)? No means GitFlow until you fix CI. Do we have manual QA before release? Yes suggests GitFlow release branches. Are we shipping a SaaS web app? Trunk-based is almost always the answer. Our Recommendation for Most Small Teams in 2026 For the majority of small teams building web applications or internal tools, we recommend a simplified trunk-based approach, often called GitHub Flow: You can read more here. One long-lived branch: main Short-lived feature/* and bugfix/* branches (max 2 days) Mandatory PR with at least 1 reviewer Squash-and-merge to keep history clean Protected main: no direct push, CI must pass Tag releases with semantic versioning If you build embedded software, mobile apps sold through app stores, or products with long-term support versions, stick with GitFlow. Common Mistakes to Avoid Long-lived feature branches. If a branch lives more than 3 days, split the work. No branch protection. Always require PR reviews and passing CI on main. Merging without rebasing. Keep history linear when possible (squash or rebase). Skipping tags. Tags are how you answer “what’s in production right now?” Copying enterprise workflows. Your team is not Google. Simpler is better. FAQ Is GitFlow dead in 2026? No. Vincent Driessen himself noted GitFlow is not ideal for web SaaS but remains valuable for versioned software (mobile apps, libraries, embedded systems). It’s not dead, just misapplied. Can a 2-person team use trunk-based development? Absolutely. In fact, trunk-based works even better with fewer people because coordination

How to Set Up a Git Branching Strategy for Small Teams: GitFlow vs Trunk-Based Development Read More »

How to Set Up Sentry for Error Tracking in a Node.js Production App

Why Sentry Node.js Error Tracking Matters in Production When a Node.js app crashes at 3 AM, your logs alone rarely tell the full story. Sentry Node.js error tracking gives you real-time stack traces, user context, breadcrumbs, and performance insights so you can fix bugs before your users complain on Twitter. In this hands-on tutorial, we at Coding4 will walk you through installing, configuring, and using Sentry in a real Node.js production application. Unlike other guides, we will also cover the things that actually bite you in production: source map uploads, smart alerting, and filtering noisy errors so your inbox stays sane. namastedev.com has a solid rundown on this. What You Will Build A Node.js (Express) app instrumented with the latest Sentry SDK Automatic error and unhandled rejection capture Performance monitoring with distributed tracing Source maps uploaded on every deploy Alert rules that only ping you when it truly matters Filters to ignore known noise (bots, health checks, ECONNRESET, etc.) Step 1: Create a Sentry Project Sign in at sentry.io and create a new project. Select Node.js as the platform (or Express if that fits better). Copy the generated DSN. You will need it in a moment. Step 2: Install the Sentry SDK For any modern Node.js project (Node 18+ recommended in 2026), install the official SDK: npm install @sentry/node @sentry/profiling-node If you use TypeScript, no extra types package is needed. Sentry ships them out of the box. Step 3: Initialize Sentry as Early as Possible Create a dedicated instrument.js (or instrument.ts) file. It must be imported before any other module so Sentry can auto-instrument your dependencies. // instrument.js const Sentry = require(‘@sentry/node’); const { nodeProfilingIntegration } = require(‘@sentry/profiling-node’); Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV || ‘development’, release: process.env.APP_RELEASE, // e.g. ‘[email protected]’ integrations: [nodeProfilingIntegration()], tracesSampleRate: 0.2, // 20% of transactions profilesSampleRate: 0.2, // 20% of sampled transactions sendDefaultPii: false }); Then in your entry file: // server.js require(‘./instrument’); const express = require(‘express’); const Sentry = require(‘@sentry/node’); const app = express(); app.get(‘/’, (req, res) => res.send(‘Hello’)); app.get(‘/boom’, () => { throw new Error(‘Test error from Coding4’); }); // The Sentry error handler must be registered before any other error middleware Sentry.setupExpressErrorHandler(app); app.use((err, req, res, next) => { res.statusCode = 500; res.end(‘Internal Server Error’); }); app.listen(3000); Hit /boom once. You should see the error appear in your Sentry dashboard within seconds. This guide goes deeper on it. Step 4: Capture Unhandled Rejections and Manual Errors The SDK already hooks uncaughtException and unhandledRejection. For business logic errors you want to capture manually, use: try { await chargeCustomer(order); } catch (err) { Sentry.captureException(err, { tags: { module: ‘billing’ }, extra: { orderId: order.id } }); throw err; } Step 5: Upload Source Maps on Every Deploy Without source maps, minified or transpiled stack traces are useless. The recommended way in 2026 is the Sentry Wizard: npx @sentry/wizard@latest -i sourcemaps It will: Create a .sentryclirc or environment variable setup Add build scripts using @sentry/cli Inject Debug IDs so source maps match no matter the release name Add these environment variables to your CI/CD: Variable Purpose SENTRY_AUTH_TOKEN Auth token with project:releases scope SENTRY_ORG Your Sentry org slug SENTRY_PROJECT Project slug APP_RELEASE Unique release identifier, e.g. git SHA In your CI pipeline, after a successful build: npx sentry-cli sourcemaps inject ./dist npx sentry-cli sourcemaps upload –release=$APP_RELEASE ./dist Step 6: Filter the Noise Nothing kills adoption faster than a dashboard full of junk. Here are filters we apply on almost every Coding4 project: Sentry.init({ // … ignoreErrors: [ ‘ECONNRESET’, ‘ETIMEDOUT’, ‘Non-Error promise rejection captured’ ], beforeSend(event, hint) { const err = hint.originalException; // Drop 404s and health check noise if (event.request?.url?.includes(‘/health’)) return null; if (err && err.status === 404) return null; // Drop known bot user agents const ua = event.request?.headers?.[‘user-agent’] || ”; if (/bot|crawler|spider/i.test(ua)) return null; return event; } }); Sampling Strategy For high-traffic services, sample smartly instead of dropping data blindly: tracesSampler: (ctx) => { if (ctx.request?.url?.includes(‘/health’)) return 0; if (ctx.request?.url?.includes(‘/checkout’)) return 1.0; // critical return 0.1; } Step 7: Configure Alerts That Actually Wake You Up In the Sentry UI, go to Alerts > Create Alert. We recommend the following baseline rules: New issue in production – notify Slack channel #alerts Issue affects more than 50 users in 1 hour – page on-call via PagerDuty or Opsgenie Regression detected – notify the author of the last release Performance: p95 latency > 2s for 5 minutes on critical endpoints Tie each alert to an environment tag (production only) to prevent staging noise. Step 8: Add Rich Context Context is what turns a stack trace into a fix. Add user and request context in your middleware: app.use((req, res, next) => { Sentry.setUser({ id: req.user?.id, email: req.user?.email }); Sentry.setTag(‘tenant’, req.tenantId); next(); }); Coding4 vs Basic Setup: What You Gain Feature Default Install Coding4 Production Setup Error capture Yes Yes + rich context Source maps Manual Automated via CI with Debug IDs Noise filtering None ignoreErrors + beforeSend + tracesSampler Alerts Email everything Tiered alerts by severity and environment Performance Off Distributed tracing + profiling Common Pitfalls to Avoid Initializing Sentry too late: it must load before Express, HTTP, and any DB driver. Missing release tag: without it, source maps will not resolve. 100% sampling in production: expensive and often useless. Sample smartly. Sending PII by accident: keep sendDefaultPii: false unless you truly need it and you are compliant. Not scrubbing secrets: use beforeSend to strip tokens from URLs and headers. FAQ Is Sentry free for Node.js? Yes, Sentry offers a free Developer plan with a monthly event quota. It is enough to get started and to instrument small services. Larger production workloads usually move to the Team or Business plan. For a real-world example, look at one agency that does this well. Does Sentry slow down my Node.js app? The overhead is minimal for error capture. Performance tracing and profiling add some cost, which is why you should use tracesSampleRate and profilesSampleRate instead of capturing 100% of traffic. How is Sentry different from a logging tool like Winston or

How to Set Up Sentry for Error Tracking in a Node.js Production App Read More »

How to Estimate Web Development Project Timelines: A Practical Framework for Agencies and Freelancers

Estimating web development projects is one of the hardest skills to master, yet it is what separates profitable agencies and freelancers from those who constantly work overtime for free. At Coding4.net, we have delivered hundreds of client projects, and we have refined a practical estimation framework that consistently keeps us within budget and on schedule. This guide walks you through exactly how we do it. Instead of giving you a generic online calculator, we are going to show you the real thinking process behind a solid estimate, including how to handle the two things most estimators forget: buffer multipliers and client feedback cycles. Why Most Web Development Estimates Fail Before diving into the framework, let us look at why estimates go wrong so often: Optimism bias: Developers estimate the happy path, not reality. Missing tasks: Setup, deployment, testing, and revisions are often forgotten. Ignoring the client factor: Feedback loops, late assets, and scope changes are guaranteed. No buffer: Estimates are given as fixed numbers instead of ranges with contingency. The framework below fixes all four issues. The 5-Step Framework to Estimate a Web Development Project Step 1: Gather the Right Information Before Estimating You cannot estimate what you do not understand. Before touching a spreadsheet, get clear answers to these questions: There’s a good explainer over at astuteo.com. What is the business goal of the site or application? How many pages or screens are required? Is there an existing design, or does it need to be created? What CMS or framework will be used? What integrations are needed (payment, CRM, analytics, ERP)? Who is providing the content, and when? What is the approval process on the client side? What are the hosting and deployment requirements? If any answer is vague, flag it. Vague answers become scope creep later. Step 2: Break the Project Into Small Tasks (WBS) Use a Work Breakdown Structure. The rule of thumb: no single task should exceed 8 hours. If it does, break it down further. Small tasks are easier to estimate accurately and easier to track. Typical categories for a business website include: Discovery and planning UX and UI design Frontend development Backend and CMS setup Integrations Content integration Testing and QA Deployment and launch Post-launch support Step 3: Apply Buffer Multipliers This is the step most freelancers skip, and it is why they lose money. After estimating each task with your best-case number, multiply it by a buffer factor based on uncertainty. Task Type Uncertainty Level Buffer Multiplier Routine work you have done many times Low x 1.2 Familiar work with some new elements Medium x 1.5 New tech, new integration, unclear specs High x 2.0 R&D, experimental features Very High x 3.0 Step 4: Account for Client Feedback Cycles Client feedback is not a delay, it is part of the project. Yet most estimates ignore it. Here is how we handle it: This write-up is worth a look. Every deliverable gets 2 rounds of revisions built into the estimate. Add calendar days for client review time (usually 2 to 5 business days per round). Distinguish clearly between working hours (what you bill) and calendar time (what the client sees on the timeline). Add a clause in your contract: extra revision rounds are billed separately. A useful rule: for every 100 hours of development work, expect 20 to 30 hours tied up in feedback, meetings, and revisions. Step 5: Convert Hours Into a Realistic Timeline Even if a project totals 200 hours, that does not mean 5 weeks of calendar time. Nobody codes 40 focused hours per week. Use these conversion rules: Productive hours per day: 5 to 6 for a solo developer Add: client review windows, holidays, parallel projects Add: 10 to 15 percent contingency at the project level for unknowns Sample Estimation Breakdown: A Typical Business Website Let us apply the framework to a realistic scenario: a 10-page business website for a mid-sized services company, built on WordPress, with a contact form, blog, newsletter integration, and multilingual support (EN/FR). Phase / Task Base Hours Buffer Final Hours Discovery workshop and requirements doc 6 x 1.2 7 Wireframes (10 pages) 10 x 1.2 12 UI design (desktop + mobile) 24 x 1.5 36 WordPress setup and theme foundation 8 x 1.2 10 Frontend development (10 templates) 40 x 1.5 60 Custom fields, blog, contact form 12 x 1.5 18 Multilingual setup (EN/FR) 8 x 2.0 16 Newsletter integration (Mailchimp/Brevo) 4 x 1.5 6 Content integration 10 x 1.2 12 SEO basics, performance, accessibility 8 x 1.5 12 QA and cross-browser testing 10 x 1.5 15 Deployment and launch 6 x 1.5 9 Project management and client meetings 20 x 1.2 24 Subtotal 166 237 Project-level contingency (12%) 28 Total estimated hours 265 hours Calendar timeline: With one developer working roughly 25 productive hours per week on this project, plus 3 client feedback rounds (approximately 10 business days total), the realistic delivery window is 12 to 14 weeks. Presenting the Estimate to the Client How you communicate the estimate matters as much as the numbers. Our recommendations: Present a range, not a single number (for example 250 to 290 hours). Show the assumptions your estimate is based on. If those assumptions change, the estimate changes. Split the price by phase so the client sees where the value is. Clearly separate fixed scope from hourly work (like ongoing content updates). Include a change request process in your proposal. Common Mistakes to Avoid Estimating in isolation: Involve the developers who will actually do the work. Forgetting non-coding time: Meetings, emails, documentation, and deployment all count. Not versioning your estimate: Keep a copy of the original scope. When it changes, issue a revised estimate. Confusing effort with duration: 100 hours of work does not equal 100 hours on the calendar. No post-project review: Compare estimated vs actual hours after every project. This is how you get better. Tools We Recommend in 2026 Notion or Google Sheets for the estimation matrix Toggl or Clockify for tracking actual

How to Estimate Web Development Project Timelines: A Practical Framework for Agencies and Freelancers Read More »

NoSQL vs SQL Databases Explained: A Practical Comparison With Real Use Cases

Choosing between NoSQL vs SQL is one of those decisions that shapes your entire application stack. Pick wrong, and you’ll fight your database for years. Pick right, and it becomes invisible infrastructure that just works. This guide skips the hype and gives you a clear, practical breakdown based on how we build applications at Coding4. We’ll look at real use cases, honest trade-offs, and a decision framework you can actually apply to your next project. What SQL and NoSQL Actually Are SQL Databases (Relational) SQL databases store data in tables made of rows and columns, with a predefined schema and strong relationships between tables. They rely on ACID transactions (Atomicity, Consistency, Isolation, Durability) to guarantee data integrity. Common examples: PostgreSQL, MySQL, Microsoft SQL Server, Oracle, MariaDB. NoSQL Databases (Non-Relational) NoSQL stands for “Not Only SQL”. These databases use flexible data models and typically fall into four categories: Document stores: MongoDB, Couchbase Key-value stores: Redis, DynamoDB Column-family stores: Cassandra, ScyllaDB Graph databases: Neo4j, Amazon Neptune NoSQL vs SQL: Side-by-Side Comparison Criteria SQL NoSQL Data Model Tables with rows and columns Documents, key-value, graph, wide-column Schema Fixed, predefined Dynamic, flexible Scalability Vertical (bigger machine) Horizontal (more machines) Transactions Strong ACID support Often eventual consistency (BASE) Query Language Standardized SQL Varies per engine Best for Complex queries, relationships High volume, unstructured data Joins Native and efficient Limited or manual When to Choose SQL SQL shines when your data is structured, your relationships matter, and consistency is non-negotiable. You can read more here. Real Use Cases for SQL Financial systems and payments: A banking transaction cannot afford eventual consistency. When money moves from account A to account B, both operations must succeed or fail together. ACID transactions in PostgreSQL or SQL Server are built for this. ERP and inventory management: Products, suppliers, orders, warehouses, invoices. This is a web of relationships that begs for foreign keys and joins. Business analytics and reporting: Complex aggregations across multiple tables (SUM, GROUP BY, window functions) are where SQL engines are unbeatable. SaaS applications with clear entities: Users, subscriptions, invoices, projects. If you can draw an entity-relationship diagram in five minutes, SQL is your friend. Applications requiring strong data integrity: Healthcare records, legal documents, government systems. When to Choose NoSQL NoSQL wins when your data is fluid, your scale is massive, or your access patterns don’t match a relational model. Real Use Cases for NoSQL Content management and catalogs (Document DB): A product catalog where each item has different attributes (a phone has RAM, a t-shirt has size and color) fits naturally in MongoDB documents. Real-time caching and sessions (Key-Value): Redis handling millions of session lookups per second with sub-millisecond latency. IoT and time-series data (Column-family): Cassandra ingesting millions of sensor readings per second across a cluster of nodes. Social networks and recommendations (Graph): “Friends of friends who liked X” is a nightmare in SQL and a native operation in Neo4j. Event logging and analytics pipelines: High-throughput writes with flexible schemas fit column stores or document stores perfectly. Mobile and offline-first apps: Document databases with sync capabilities (Couchbase, Firestore) simplify replication logic. The Honest Trade-offs Nobody Talks About SQL Downsides Schema migrations on large tables can lock production Vertical scaling has a hard ceiling and gets expensive fast Rigid structure slows down early-stage product iteration NoSQL Downsides No schema means the schema lives in your application code, and bugs follow Joins done in the app layer are slow and error-prone Eventual consistency is a footgun if you don’t understand it Ad-hoc analytics queries are painful compared to SQL A Decision Framework You Can Actually Use Ask these questions in order. Stop at the first clear answer. Is my data highly relational? If yes, start with SQL. Do I need strict ACID transactions across multiple entities? If yes, SQL. Is my schema going to change frequently or vary per record? If yes, lean NoSQL (document). Am I dealing with millions of writes per second or planetary scale? If yes, NoSQL (column-family or key-value). Are my main queries about relationships (paths, networks)? If yes, graph database. Do I need blazing-fast lookups by a single key? If yes, key-value store. Can You Use Both? Yes, and You Probably Should The best architectures we build at Coding4 rarely rely on a single database. A common pattern: PostgreSQL for the core transactional business data Redis for caching, sessions, and rate limiting Elasticsearch or MongoDB for full-text search or flexible document storage ClickHouse or a column store for analytics This is called polyglot persistence: use the right tool for each workload instead of forcing one database to do everything. We break it down further here. Modern SQL Is Not the SQL of 2010 One thing worth noting in 2026: PostgreSQL now handles JSON, key-value, full-text search, and even vector search for AI workloads. The gap between SQL and NoSQL has narrowed significantly. For many projects, a modern PostgreSQL setup can replace three or four specialized databases and keep operational complexity low. Frequently Asked Questions Which is better, NoSQL or SQL? Neither is universally better. SQL is better for structured, relational data with strong consistency needs. NoSQL is better for unstructured data, massive horizontal scale, or flexible schemas. The right choice depends entirely on your use case. Is MongoDB SQL or NoSQL? MongoDB is a NoSQL document database. It stores data as flexible BSON documents rather than in rows and columns. What are the 4 types of NoSQL databases? Document (MongoDB, Couchbase) Key-Value (Redis, DynamoDB) Column-family (Cassandra, HBase) Graph (Neo4j, Neptune) Is NoSQL faster than SQL? NoSQL is often faster for simple read/write operations at scale, especially with horizontal partitioning. SQL is typically faster for complex queries involving joins and aggregations on well-indexed data. “Faster” always depends on the query pattern. Can NoSQL replace SQL entirely? For most business applications, no. Even companies operating at massive scale usually keep a relational database for core transactional data alongside NoSQL systems for specific workloads. Does SQL work well with AI and vector search? Yes. PostgreSQL with

NoSQL vs SQL Databases Explained: A Practical Comparison With Real Use Cases Read More »

How to Build a Progressive Web App from Scratch: A Step-by-Step Tutorial with Service Workers

If you’ve ever wondered how to build a progressive web app that installs like a native app, works offline, and loads in milliseconds, you’re in the right place. In this hands-on tutorial, we’ll turn a plain HTML/CSS/JS project into a fully working PWA. No frameworks required, no fluff, just code you can copy, tweak, and ship today. By the end of this guide, you’ll have a working demo project with a valid manifest, a registered service worker, offline caching, and a custom install prompt. What is a Progressive Web App (and why should you care in 2026)? A Progressive Web App (PWA) is a website built with standard web technologies (HTML, CSS, JavaScript) that behaves like a platform-specific app. It can be installed on the home screen, launched in its own window, receive push notifications, and work offline thanks to a service worker. PWAs are very much still a thing in 2026. Browser support is now universal, Apple has significantly improved iOS PWA capabilities, and Microsoft, Google, and Samsung stores all accept PWAs as regular apps. For most small-to-medium projects, a PWA replaces the need to maintain three separate codebases (web, iOS, Android). PWA vs Native App: quick comparison Feature PWA Native App Codebase Single (web) One per platform Distribution URL or app store App store only Install size Usually less than 1 MB 20 MB and up Offline support Yes (via service worker) Yes Update process Automatic on next visit Manual store update Development cost Low High Prerequisites Basic knowledge of HTML, CSS, and JavaScript A code editor (VS Code recommended) Node.js installed (we’ll use it to serve files over HTTPS locally) A modern browser (Chrome, Edge, Firefox, or Safari) Important: PWAs require HTTPS. localhost is treated as secure, so we can test locally without a certificate. Step 1: Create the Demo Project Structure Let’s build a tiny “Quick Notes” app that saves notes locally and works offline. Create the following folder structure: quick-notes-pwa/ ├── index.html ├── styles.css ├── app.js ├── sw.js ├── manifest.webmanifest └── icons/ ├── icon-192.png └── icon-512.png index.html <!DOCTYPE html> <html lang=”en”> <head> <meta charset=”UTF-8″> <meta name=”viewport” content=”width=device-width, initial-scale=1.0″> <meta name=”theme-color” content=”#2563eb”> <link rel=”manifest” href=”manifest.webmanifest”> <link rel=”icon” href=”icons/icon-192.png”> <link rel=”stylesheet” href=”styles.css”> <title>Quick Notes PWA</title> </head> <body> <header> <h1>Quick Notes</h1> <button id=”installBtn” hidden>Install App</button> </header> <main> <textarea id=”noteInput” placeholder=”Type a note…”></textarea> <button id=”saveBtn”>Save note</button> <ul id=”noteList”></ul> </main> <script src=”app.js”></script> </body> </html> styles.css * { box-sizing: border-box; font-family: system-ui, sans-serif; } body { margin: 0; padding: 1rem; background: #f5f5f5; } header { display: flex; justify-content: space-between; align-items: center; } textarea { width: 100%; min-height: 100px; padding: 0.5rem; } button { background: #2563eb; color: white; border: none; padding: 0.6rem 1rem; border-radius: 6px; cursor: pointer; } ul { list-style: none; padding: 0; } li { background: white; padding: 0.75rem; margin: 0.5rem 0; border-radius: 6px; } Step 2: Create the Web App Manifest The manifest tells the browser how your app should appear when installed. Create manifest.webmanifest: { “name”: “Quick Notes”, “short_name”: “Notes”, “description”: “A tiny offline-first notes app.”, “start_url”: “/”, “scope”: “/”, “display”: “standalone”, “orientation”: “portrait”, “background_color”: “#ffffff”, “theme_color”: “#2563eb”, “icons”: [ { “src”: “icons/icon-192.png”, “sizes”: “192×192”, “type”: “image/png”, “purpose”: “any maskable” }, { “src”: “icons/icon-512.png”, “sizes”: “512×512”, “type”: “image/png”, “purpose”: “any maskable” } ] } Key manifest properties explained name / short_name: Full name and home screen label start_url: The URL loaded when the app launches display: standalone hides the browser UI theme_color: Color of the OS toolbar icons: You need at least a 192×192 and a 512×512 icon for installability Step 3: Register the Service Worker A service worker is a JavaScript file that runs in the background, separate from your page. It intercepts network requests, enabling offline support and caching. Add this to your app.js: // Register the service worker if (‘serviceWorker’ in navigator) { window.addEventListener(‘load’, () => { navigator.serviceWorker.register(‘/sw.js’) .then(reg => console.log(‘SW registered:’, reg.scope)) .catch(err => console.error(‘SW registration failed:’, err)); }); } // Basic notes logic using localStorage const input = document.getElementById(‘noteInput’); const saveBtn = document.getElementById(‘saveBtn’); const list = document.getElementById(‘noteList’); function loadNotes() { const notes = JSON.parse(localStorage.getItem(‘notes’) || ‘[]’); list.innerHTML = notes.map(n => `<li>${n}</li>`).join(”); } saveBtn.addEventListener(‘click’, () => { const text = input.value.trim(); if (!text) return; const notes = JSON.parse(localStorage.getItem(‘notes’) || ‘[]’); notes.unshift(text); localStorage.setItem(‘notes’, JSON.stringify(notes)); input.value = ”; loadNotes(); }); loadNotes(); Step 4: Write the Service Worker with Offline Caching Now create sw.js. This is where the magic happens: we cache static assets on install and serve them from cache when the network is unavailable. const CACHE_NAME = ‘quick-notes-v1’; const ASSETS = [ ‘/’, ‘/index.html’, ‘/styles.css’, ‘/app.js’, ‘/manifest.webmanifest’, ‘/icons/icon-192.png’, ‘/icons/icon-512.png’ ]; // Install: pre-cache the app shell self.addEventListener(‘install’, event => { event.waitUntil( caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS)) ); self.skipWaiting(); }); // Activate: clean up old caches self.addEventListener(‘activate’, event => { event.waitUntil( caches.keys().then(keys => Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))) ) ); self.clients.claim(); }); // Fetch: cache-first strategy with network fallback self.addEventListener(‘fetch’, event => { if (event.request.method !== ‘GET’) return; event.respondWith( caches.match(event.request).then(cached => { return cached || fetch(event.request).then(response => { return caches.open(CACHE_NAME).then(cache => { cache.put(event.request, response.clone()); return response; }); }).catch(() => caches.match(‘/index.html’)); }) ); }); Caching strategies at a glance Strategy Best for Behavior Cache-first Static assets (CSS, JS, images) Serve from cache, fall back to network Network-first API calls, dynamic content Try network, fall back to cache Stale-while-revalidate Frequently updated content Serve cache immediately, update in background Cache-only Pre-cached shell files Only serve from cache Step 5: Add a Custom Install Prompt Browsers fire a beforeinstallprompt event when your app is installable. Capture it and show your own button for better UX. Append this to app.js: let deferredPrompt; const installBtn = document.getElementById(‘installBtn’); window.addEventListener(‘beforeinstallprompt’, (e) => { e.preventDefault(); deferredPrompt = e; installBtn.hidden = false; }); installBtn.addEventListener(‘click’, async () => { if (!deferredPrompt) return; deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice; console.log(`Install outcome: ${outcome}`); deferredPrompt = null; installBtn.hidden = true; }); window.addEventListener(‘appinstalled’, () => { console.log(‘PWA installed successfully’); }); Step 6: Test Your PWA Locally Service workers won’t run from file://. Use a local HTTPS-friendly server: Open a terminal in your project folder Run npx serve .

How to Build a Progressive Web App from Scratch: A Step-by-Step Tutorial with Service Workers Read More »

Microservices vs Monolithic Architecture: When to Choose Each in 2026

Every few months, a new engineering team at a startup decides to build their MVP with 14 microservices, Kubernetes, a service mesh, and event-driven communication between everything. Six months later, they’re drowning in complexity, deployments take hours, and they can’t figure out why a simple bug fix touches 5 repositories. The microservices vs monolithic debate has been going on for years, but in 2026 the conversation has shifted. After watching countless teams over-engineer their systems, the industry is finally admitting something obvious: most applications should start as monoliths. At Coding4, we’ve helped clients migrate in both directions, and this article shares the practical trade-offs we see every day. The Core Difference in Plain English A monolithic architecture packages your entire application as a single deployable unit. One codebase, one database (usually), one deployment pipeline. Everything runs together. A microservices architecture breaks the application into small, independently deployable services. Each service owns its data, has its own deployment pipeline, and communicates with other services via APIs or messaging. That’s it. Everything else, the containers, the orchestration, the service mesh, is just tooling that surrounds these two fundamentally different approaches. The Honest Comparison Table Criteria Monolithic Microservices Initial development speed Fast Slow Operational complexity Low High Debugging Straightforward, single stack trace Distributed tracing required Team autonomy Limited, everyone touches the same code High, teams own their services Scaling Scale the entire app Scale individual services Deployment Deploy everything together Independent deployments Infrastructure cost Low Significantly higher Failure isolation Poor, one bug can crash everything Good, if designed properly Team size sweet spot 1 to 20 developers 50+ developers When Monolithic Architecture Wins Contrary to what conference talks might suggest, the monolith is not a legacy pattern. It’s often the correct choice. Here are concrete scenarios where you should absolutely build a monolith: 1. You’re building an MVP or early-stage product You don’t know what your product will look like in six months. Domain boundaries are unclear. Requirements change weekly. In this situation, microservices force you to commit to boundaries you don’t yet understand. A monolith lets you refactor freely. 2. Your team has fewer than 20 developers Microservices solve organizational problems. If everyone can fit in one meeting room, you don’t have those problems yet. The coordination overhead of distributed systems will slow you down more than a monolith ever would. 3. Your traffic is predictable and moderate If you serve a few thousand requests per second on well-defined endpoints, a properly built monolith on a beefy server or two will handle it. You don’t need independent scaling for services that all get similar load. 4. Your domain is tightly coupled Some domains, like accounting systems or CRUD-heavy business apps, have entities that constantly interact. Splitting them into microservices creates chatty, latency-prone communication for no real benefit. When Microservices Actually Make Sense Microservices are the right choice when specific pressures build up. Here are the real signals: Multiple teams stepping on each other in the same codebase, causing constant merge conflicts and coordination meetings. Different scaling needs per component, for example a video encoding pipeline that needs GPU nodes while your API layer runs fine on small instances. Different technology requirements, such as needing Python for machine learning and Go for high-throughput services. Independent release cadences, where the mobile team ships daily but the payment team needs quarterly compliance reviews. Regulatory isolation, when certain data (payment, health, PII) must be physically separated from the rest of the system. Very high scale, where a single deployable unit genuinely cannot handle the traffic anymore. Notice what’s not on this list: “because Netflix does it” or “because it’s modern.” Netflix has 2000+ engineers and streams to 250 million users. Your SaaS with 40 customers is not Netflix. The Modular Monolith: The Overlooked Middle Ground In 2026, the modular monolith has become the pragmatic default for most serious projects. It gives you 80% of the benefits of microservices with 20% of the operational cost. A modular monolith enforces strict internal boundaries: Each module has its own well-defined public API Modules cannot access each other’s database tables directly Communication goes through explicit interfaces, not shared internals Everything still deploys as one unit The beauty is that when a specific module truly needs to be extracted as a microservice later, the work is straightforward because the boundary is already clean. You get evolvability without paying the distributed systems tax upfront. The Hidden Costs of Microservices Nobody Mentions When teams pitch microservices, they focus on the benefits. Here’s what they often forget: Distributed tracing infrastructure becomes mandatory, not optional Data consistency requires patterns like Saga, outbox, or eventual consistency, all of which are hard Local development becomes complicated (do you run 15 services on your laptop?) Testing requires contract tests, integration environments, and much more discipline Network failures become a daily reality you must design for Observability tooling costs add up fast (Datadog bills can shock you) DevOps headcount grows to keep the platform running A Decision Framework for 2026 Here’s the practical checklist we use with clients at Coding4: Do you have more than 3 development teams working on the same product? If no, use a monolith. Are teams actively blocked by each other in the current setup? If no, use a monolith. Do you have dedicated platform or DevOps engineers? If no, use a monolith. Do specific components have genuinely different scaling profiles? If no, use a monolith. Is your domain mature and well understood? If no, use a modular monolith and extract services later. If you answered yes to most of these, then microservices might be justified. Otherwise, save yourself the pain. What About Amazon, Netflix, and Uber? Yes, all the tech giants use microservices. But here’s the part rarely mentioned: they all started as monoliths. Amazon’s early architecture was a monolith. Netflix moved to microservices only after their DVD-rental monolith started failing at scale. Uber famously went from monolith to microservices, and then partially back to “macroservices” because they’d fragmented too much. The pattern

Microservices vs Monolithic Architecture: When to Choose Each in 2026 Read More »

How to Prepare Your iOS App for App Store Submission: A 2026 Step-by-Step Guide

If you’re wondering how to submit app to App Store without getting stuck in the dreaded review loop, you’re in the right place. After shipping dozens of iOS apps at Coding4, we’ve compiled a no-nonsense, 2026-updated checklist that skips the fluff and focuses on what actually matters: getting approved on the first try. This guide walks you through App Store Connect setup, code signing, TestFlight beta testing, screenshots, metadata, and the most common rejection reasons we see in 2026. Before You Start: The Prerequisites Before you even open Xcode with submission in mind, make sure you have the following ready: An active Apple Developer Program membership (99 USD/year, or free for approved non-profits and government entities) Xcode 16 or later installed on macOS Sequoia or newer A physical iOS device for real-world testing Your app’s bundle identifier registered App icons in all required sizes (1024×1024 for the store, plus in-app icons) A privacy policy URL (mandatory since 2024, strictly enforced in 2026) Step 1: Configure App Store Connect App Store Connect is where everything happens outside of Xcode. Here’s the exact flow: Go to App Store Connect and sign in with your Apple Developer account Click My Apps, then the + button in the top left Select New App Fill in the required fields: Platform (iOS, macOS, visionOS, etc.) App name (up to 30 characters, must be unique) Primary language Bundle ID (must match your Xcode project) SKU (internal identifier, not shown to users) User access Fill Out the App Information Once the app record exists, complete these sections carefully: App Information: category (primary and secondary), content rights, age rating Pricing and Availability: free or paid, territories App Privacy: this is where most first-time submissions fail. Declare every piece of data you collect and whether it’s linked to the user Step 2: Code Signing Without Losing Your Mind Code signing is where seasoned developers still make mistakes. In 2026, Apple has continued to refine automatic signing, and we recommend using it unless you have a specific enterprise reason not to. Open your project in Xcode Select your target, then the Signing & Capabilities tab Check Automatically manage signing Select your team Ensure the bundle identifier matches exactly what’s in App Store Connect If you use manual signing, make sure your distribution certificate is valid and your provisioning profile includes all required entitlements (Push Notifications, App Groups, Sign in with Apple, etc.). Step 3: TestFlight Beta Testing (Do Not Skip This) Skipping TestFlight is the fastest way to a rejected build. Use it, even for solo projects. Archive and Upload In Xcode, select Any iOS Device (arm64) as your build target Go to Product > Archive When the Organizer opens, click Distribute App Choose App Store Connect, then Upload Wait for processing (usually 5 to 30 minutes) Internal vs External Testing Type Testers Review Required? Best For Internal Up to 100 team members No QA and dev team External Up to 10,000 users Yes (Beta App Review) Public beta Run at least one week of TestFlight before submitting to the App Store. Real-world feedback catches crashes and UX issues that simulators miss. Step 4: Screenshots and App Previews As Reddit developers often point out, generating screenshots is often the most tedious part of publishing. Here’s what you need for 2026: iPhone 6.9″ display (iPhone 16 Pro Max): required iPhone 6.5″ or 6.7″ display: recommended for older device coverage iPad 13″ display (M4 iPad Pro): required if your app supports iPad Between 3 and 10 screenshots per size Optional: App Previews (video, 15 to 30 seconds) Pro tip: use tools like Fastlane’s snapshot, Screenshot Studio, or Rotato to automate localized screenshot generation. Trust us, doing this manually for 10 languages will end you. Step 5: Metadata That Passes Review Your metadata is what convinces both users and the review team. Fill these out with care: Subtitle: 30 characters, shows under the app name Promotional Text: 170 characters, editable without a new submission Description: up to 4,000 characters. Be clear about what the app does Keywords: 100 characters, comma-separated, no spaces Support URL and Marketing URL: must be live and functional What’s New in This Version: required for updates Step 6: Submit for Review Once everything is filled in: Go to the app’s version page in App Store Connect Select the build you uploaded via Xcode Answer the Export Compliance questions (encryption usage) Answer the Content Rights and Advertising Identifier questions Choose your release option: automatic, manual, or scheduled Click Add for Review, then Submit to App Review Average review time in 2026 is around 24 to 48 hours, though complex apps can take longer. The Top 10 Rejection Reasons in 2026 (And How to Avoid Them) This is where we differ from other guides. Here’s what actually gets apps rejected right now, based on our recent submissions: Guideline 2.1 – Performance (Crashes and Bugs): test on a real device, not just simulators. Reviewers use physical devices. Guideline 5.1.1 – Data Collection Without Justification: if you ask for camera, location, or contacts access, explain exactly why in your Info.plist usage strings. Guideline 4.0 – Poor Design: minimum viable functionality is no longer enough. Your UI must feel native. Guideline 3.1.1 – In-App Purchase: if you sell digital content, you must use Apple’s IAP system. External payment links are heavily restricted. Guideline 2.3.3 – Inaccurate Screenshots: screenshots must reflect the actual app. Marketing mockups with features that don’t exist will get rejected. Guideline 5.1.2 – Sign in with Apple: if you offer third-party login (Google, Facebook), you must also offer Sign in with Apple. Guideline 4.8 – Login Services: same as above, plus data minimization requirements. Missing Privacy Policy URL: still a common miss. It must be live before submission. AI Content Disclosure: if your app uses generative AI, you must now declare it in the App Privacy section and moderate outputs. Guideline 2.5.1 – Non-Public APIs: automated scanners catch these instantly. Do not use private frameworks. Post-Submission: What to Do While You

How to Prepare Your iOS App for App Store Submission: A 2026 Step-by-Step Guide Read More »