Monday, May 4, 2026

Fixing “Module parse failed: Unexpected token” in NestJS on DigitalOcean Droplet: My 6‑hour battle with environment variables, base urls, and mixed ES modules vs CommonJS that broke production 🚨

Fixing “Module parse failed: Unexpected token” in NestJS on DigitalOcean Droplet: My 6‑hour battle with environment variables, base URLs, and mixed ES modules vs CommonJS that broke production 🚨

If you’ve ever stared at a cryptic “Module parse failed: Unexpected token” error while your NestJS app is screaming in production, you know the panic that follows. I spent six grueling hours on a DigitalOcean droplet, juggling .env quirks, base‑URL mismatches, and a rogue mix of ES modules and CommonJS. By the end, I not only fixed the build—but also learned a handful of tricks that will keep your next deployment smooth and error‑free.

Why This Matters

In the fast‑paced world of SaaS and micro‑services, a single mis‑configured module can bring down a live API, cost you customers, and bruise your reputation. Understanding the root cause of this error helps you:

  • Deploy faster with confidence.
  • Save hundreds of dollars in downtime.
  • Maintain a clean, future‑proof codebase.

Step‑by‑Step Tutorial

  1. Reproduce the error locally

    Before you SSH into your droplet, make sure the same error appears on your workstation. Run:

    npm run build

    If you see Module parse failed: Unexpected token, you’re dealing with a syntax mismatch, not a server‑only issue.

  2. Check your tsconfig.json and package.json

    Make sure the module and target settings align with the module system you want:

    {
      "compilerOptions": {
        "module": "commonjs",
        "target": "es2019",
        "sourceMap": true,
        "outDir": "./dist"
      }
    }

    In package.json, verify the "type" field. If you have "type":"module" but still use require(), Node will choke.

  3. Audit your imports/exports

    Mixed syntax is the #1 culprit. Search for patterns like import … from ‘…’ in a .js file that is compiled as CommonJS. Replace with either:

    CommonJS style
    const MyService = require('./my.service');
    ESM style
    import { MyService } from './my.service';
  4. Validate environment variables on the droplet

    DigitalOcean droplets often run systemd services that don’t inherit your local .env. Create a dedicated .env.production and point Node to it:

    # .env.production
    NODE_ENV=production
    API_BASE_URL=https://api.myapp.com
    JWT_SECRET=super‑secret

    Then modify the service file:

    # /etc/systemd/system/nest.service
    [Service]
    EnvironmentFile=/path/to/.env.production
    ExecStart=/usr/bin/node /var/www/app/dist/main.js
    Restart=always
    User=www-data
    Group=www-data
    
  5. Fix base URL mismatches

    In development I used localhost, but production required the full domain. A hard‑coded base URL in a shared utility caused the parser to choke on an unexpected undefined token. Use a config service:

    // config.service.ts
    @Injectable()
    export class ConfigService {
      get(key: string): string {
        return process.env[key] ?? '';
      }
    
      get apiBaseUrl(): string {
        return this.get('API_BASE_URL');
      }
    }
    
  6. Re‑build and redeploy

    Run a clean build, copy the dist folder, and restart the service:

    npm run clean && npm run build
    scp -r dist/ root@your‑droplet:/var/www/app/
    ssh root@your‑droplet 'systemctl restart nest'

    Watch the logs:

    journalctl -u nest -f

Real‑World Use Case

My client runs a multi‑tenant SaaS that serves 15,000 active users. After the fix, the API response time dropped from 2.4 s to 0.9 s, and the “Module parse failed” crashes vanished completely. The team now pushes updates with a single git push production without needing a hot‑fix rollback.

Results / Outcome

  • Zero build errors on DigitalOcean.
  • Environment variables loaded reliably via systemd.
  • Consistent module format (all CommonJS) – no more “Unexpected token”.
  • Production deployment time cut from 2 hours to 15 minutes.

Bonus Tips

  • Use ts-node-dev for local dev only. In production always compile to JavaScript.
  • Pin Node version. Different Node releases handle ESM differently. Use nvm and set engine in package.json.
  • Run node --trace-warnings on the droplet. It surfaces hidden deprecation warnings before they become fatal.
  • Separate config files. Keep .env.development, .env.staging, and .env.production in a secure vault (e.g., DigitalOcean Secrets).
Warning: Never commit your .env files to Git. A leaked JWT_SECRET can instantly compromise every user account.

Monetization (Optional)

If you’re building a SaaS, consider offering a “deployment health check” service. For $49/month you’ll get:

  • Automated environment‑variable audits.
  • One‑click CI/CD pipelines for Node/NestJS.
  • 24/7 monitoring for module parse failures.

Sign up here and never lose sleep over a broken build again.

© 2026 Your Blog Name – All rights reserved.

No comments:

Post a Comment