Flutter, But Organized: A Starter Template That Won’t Make You Cry in Debug

SR
Serendeep Rudraraju
July 05, 20255 min read
Flutter, But Organized: A Starter Template That Won’t Make You Cry in Debug

Let’s be real-starting a new Flutter project can get messy, fast. You want to build something cool, but before you know it, you’re knee-deep in tangled folders, mystery bugs, and “wait, where does this go?” headaches.

I know because I’ve been there. My first Flutter starter template? It started out with the best intentions, but after a few rounds of “just one more feature” and some quick fixes, it turned into a cluttered, unmaintainable mess. Every change made things worse. Eventually, I realized it would be easier (and way less stressful) to start from scratch than to keep pulling my hair out trying to fix that chaos.

So I did. And this time, I built it right.

Meet my new Flutter Starter Template—designed to keep your code clean, your state predictable, and your sanity intact.


Why Did I Need a New Starter?

My old setup? It was a mess:

  • Folders everywhere.
    UI, logic, data—mixed up like spaghetti.
  • State all over the place.
    Sometimes Bloc, sometimes setState, sometimes… who knows.
  • Navigation roulette.
    Accidentally landing on the wrong page? Been there.
  • Networking pain.
    Error handling? Interceptors? Not even close.
  • No offline support.
    Lose Wi-Fi, lose your app.
  • Auth?
    JWT, but duct-taped together.
  • Environments?
    Changing from dev to prod meant hunting through files.
  • Testing?
    “It works on my machine” isn’t a test.

I wanted something better. Here’s what I built.


What’s in the Box?

Clean Architecture: No More Spaghetti

Data, Domain, UI—each in their own lane. No more “where does this go?” Just clear, logical structure.

State Management: Bloc, Done Right

Predictable, testable, and no more “why did my widget just rebuild?” moments. Bloc keeps your state where it belongs.

Navigation: GoRouter + Route Guards

No more “Oops, wrong page!” surprises. GoRouter handles your routes, guards keep your users where they should be.

Networking: Dio with Superpowers

Custom interceptors, centralized error handling, and a single place to tweak your API calls. No more copy-paste code.

Offline-First Storage: Hive

Your app works even when Wi-Fi ghosts you. Data stays local, syncs when it can.

Auth Flow: JWT, Locked Down

Login and registration with JWT. Secure, simple, and ready for real-world use.

Multi-Environment Setup: Flip a Flag

Dev, Staging, Prod—switch with a single flag. No more hunting through config files.

Theme System: Material 3, Light-Blue, Dark Mode

Modern look, easy to tweak, and dark mode built in.

Mock vs Real APIs: One Toggle

Switch from dev stubs to live data with a single toggle. Perfect for testing.

CI/CD Ready: GitHub Actions + Pre-Commit Hooks

Linting, formatting, and tests run automatically. Your code stays clean, your builds stay green.

Testing Suite: Unit, Widget, Integration

Tests out of the box. No more “I’ll add tests later” guilt.

Responsive by Default

Phones, tablets, web—your app just works everywhere.

Detailed Docs: Like a Lego Set

Step-by-step docs walk you through everything. No guesswork, just building.


The Unconventional Bit: Why Is There a package.json in My Flutter Project?

If you’re used to Dart and Flutter, you probably expect to see pubspec.yaml, not package.json. But open up my Flutter Starter Template, and there it is, right next to your pubspec.yaml and all the usual suspects.

So… why?

Because Flutter Devs Deserve Good Tooling, Too

Let’s be honest:
The Dart ecosystem is great, but when it comes to developer tooling—especially around automation, hooks, and code quality—JavaScript has been playing this game a lot longer. There’s a whole world of tools out there that just work better (or only work) with Node.

So I thought: why not steal the best parts?

What Does package.json Actually Do Here?

It’s not about running JavaScript code in your Flutter app.
It’s about making your development workflow smoother, faster, and less painful.

Here’s what you get:

  • Pre-commit hooks with Husky
    Want to make sure your code is linted, formatted, and tested before every commit? Husky + Node scripts make it easy.
  • Commit message linting with Commitlint
    Enforce conventional commit messages, so your git history is clean and readable.
  • Scriptable automation
    Need to install hooks, run code generation, or clean up your workspace? Just run npm run setup or any of the other handy scripts.
  • CI/CD glue
    Some CI tools expect a package.json for running scripts, even if your main codebase is Dart.

Example: The Power of Scripts

Check out these scripts from the template:

json
"scripts": {
  "setup": "flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs && npm run hooks:install",
  "build:dev": "flutter build apk --flavor dev -t lib/main_dev.dart",
  "test": "flutter test",
  "lint": "flutter analyze && dart format --output=none --set-exit-if-changed .",
  "pre-commit": ".githooks/pre-commit",
  "hooks:install": "node scripts/setup-hooks.js"
}

You get one-liners for everything:
Setup, build, test, lint, install hooks—no more copy-pasting long commands or forgetting steps.

Why Not Just Use Dart for This?

You could, but:

  • The Node ecosystem is mature and battle-tested for this kind of workflow.
  • Tools like Husky and Commitlint are basically the gold standard for git hooks and commit hygiene.
  • It’s cross-platform and works out of the box for most devs (since Node is everywhere).

Does This Make My Flutter App a Node App?

Nope.
Your app is still 100% Dart and Flutter.
package.json is just there to make your development life easier.
When you build your app, none of this ships to your users.


The Bottom Line

My first starter got so cluttered that fixing it felt impossible. So I started over, and this time, I built the kind of template I wish I’d had from the start—clean, robust, and with a few unconventional tricks (like package.json) to make your workflow smoother.

Want to see how it all fits together?
Check out the repo and peek at the scripts.


Got Thoughts? Open an Issue!

No comments section here, but I’d love to hear what you think.
Open an issue on GitHub if you have ideas, questions, or just want to rant about unconventional tooling.

Happy coding! 🚀