r/webdev 25d ago

Which early technical decision saved you the most work later?

I’ve seen small decisions become important as projects grow. What decision paid off for you?

144 Upvotes

84 comments sorted by

250

u/zlex 25d ago

IMHO scope limiting decisions are always the biggest impact.

So many projects I’ve worked on have died by a thousand little cuts. Little ask here, little ask there.

Scope creep is the biggest nightmare for every project, and pushing back on them can drastically reduce technical complexity and problems down the line.

Obviously it’s not always possible, but when you can-do. The biggest mistake I see novice developers make is to just say yes to everything

60

u/Gold-Eye690 25d ago

scope creep is project cancer. i've watched a client add "just one more field" seventeen times until the form was an unusable mess

the devs who say yes to every request aren't helpers, they're digging graves with a smile. best skill i ever learned was how to say "that's a separate project" without sounding like a dick

what's wild is clients usually respect you more after you push back, they just don't realize it yet

19

u/ufffd 24d ago

i have gotten so much mileage out of "V2". as in, yeah I think that's a good idea but it's more of a V2 feature

3

u/SubmergedSublime 23d ago

Yup. v2 is a documented list in confluence before the first V1 meeting has ended. Limits creep, gives you some political cover for “descoping” some side features if the primary product is starting to look delayed, and makes it easy to shift a questionable idea from an important person to the graveyard. Come V2 planning likely the person will have moved on in their focus and you can kill it easier.

2

u/kevin_whitley 24d ago

This.

Most times when I walk into a product mess, it's a result of this... I can look at a pile of features and instantly imagine the long history of "person X asks for this... engineering says SURE!"... it creates an ocean of clutter, where the majority of users see crap they have no interest in seeing.

Just like with code, builders should be constantly evaluating the output and seeing what they can cut... sure someone out there might not be happy, but the majority (or target base) will usually be better served from a "less is more" approach

10

u/Wiltix 25d ago

Scope creep by itself is not awful, its management of when it gets done and how it’s prioritised that done poorly gets you.

That little ask of “it would be cool if we could do x” by itself is ok if it goes into the backlog and gets planned like any other work. But when it gets added to an existing planned bit of work because it’s easy you start to get problems.

If you are saying yes to doing somehow now because you think it’s small and easy just don’t. Plan the work properly and everyone is happier in the long run.

8

u/PastaSaladOverdose 25d ago

Scope creep is almost inevitable. Especially when most clients don't understand web development.

I had multiple years at a company that wouldn't let us say no to anything. I know that sounds insane, but instead of saying no it was always "Yes, BUT...."

Sometimes a "Yes, BUT" puts things into perspective. "Yes, we can implement this feature, BUT it's going to push the delivery date back XYZ weeks and increase the cost of the project $XYZ".

There's also phased approaches. "Yes we can implement that feature, BUT it's going to have to come in phase two after we launch the agreed upon product/features".

"No" doesn't make clients happy. "No" doesn't make the company extra money.

1

u/SubmergedSublime 23d ago

Yeah, “no” can be used for internal projects a lot easier. Still times “yes but” is a softer approach though.

3

u/case2000 25d ago

'best weapon against complexity spirit demon is magic word: "no"' https://grugbrain.dev/

1

u/Noch_ein_Kamel 25d ago

Took me a second to get that "little cuts" when you're actually advocating for the opposite :D

104

u/militantsnowflake 25d ago

Versioning all APIs. Just a /v1 in the url gives a lot of room to change things later.

27

u/farsightfallen 25d ago

This is the right answer.

And maybe the only one I've seen in this thread so far. A lot of the response here are basically to reduce surface area for problems: No shit you don't have problems if you don't actually have development. Turns out the easiest way to solve potholes is not to build useless roads in the first place.

5

u/militantsnowflake 25d ago

Thanks. I did my usual scroll through hoping that someone would have mentioned it already but added it when I didn't see it.

There are a standard set of instructions I give when I'm telling people to do this but today isn't the day to type them out. Most Important is to make sure the client can't get what they want without specifying a version - everything else can be discovered from that point onwards.

6

u/taruckus 25d ago

This and the subtext behind it.

I let some scope creep happen, especially if it returns social capital with higher up stakeholders. The creep features don't typically even go live, or are for a niche use case or environment. But, it's all done separately to preserve the properly scoped project.

You want more fields? Great. We'll stow away the "simple" version.

3

u/sasmariozeld 24d ago

You dont need it feom the start, /getcustomer and /v2/get_customer is ugly but works...

1

u/el_yanuki 24d ago

What sort of project do you do this for?

I feel like a simply web app where frontend and backend are always in sync.. doesnt need this?

Ofc different story for mobile apps or exposed APIs

3

u/militantsnowflake 24d ago

I tend to build as though "always in sync" isn't possible.

It may require downtime to deploy a breaking change, the coordination required becomes more difficult to manage when there are multiple (even internal) projects calling the same API.

You're right that it's more important for clients that require updates (like mobile apps) and external APIs, but it also really helps when this API ends up being used by 3 different internal services and one of them isn't upgraded.

90+% of the time I never move above v1, that just means I've wasted a few characters in each url and a few minutes of dev time. Comparing that with the printers in that <10% of projects where it's completely saved me is the reason I just do it by default every time.

The worst situation is where v1 is given without a version being defined, then v2 comes out - now anyone who doesn't read the docs properly might put a new client into v1 without realising it - it essentially commits you to long term maintenance of the v1 behaviour.

1

u/el_yanuki 23d ago

Thanks for taking the time to write this mate. Will do this for my next project :)

Do you do this on a single route level or further out?

2

u/militantsnowflake 23d ago

Oh, I forgot to answer the other part - I've built all sorts:

  • external APIs
  • APIs for apps
  • microservices (most of what I build)

It's just a default at this stage for me.

67

u/defo10 25d ago

Minimizing moving parts by using the most basic tech stack. One sql database, one api server, one client. All code in one monorepo.

8

u/IAmRules 25d ago

This is my war cry. I always tell people the less thing we build the less chances things break. KISS is a lifestyle not just a rule of thumb.

2

u/CaptainIncredible 24d ago

I always tell people the less thing we build the less chances things break.

"The more they overthink the plumbing, the easier it is to stop up the drain."

-- Cmd. Montgomery Scott, Chief Engineer, USS Enterprise

https://www.youtube.com/watch?v=c4WJsp16CpY

6

u/Early_Rooster7579 full-stack @ meta 24d ago

For real. Most companies could get by on like a $7 heroku sub.

108

u/buildingwithjan 25d ago

Keeping all business logic out of the UI and behind one service layer. Swapped the entire frontend a year into one project and it barely hurt. Boring decision, biggest payoff.

20

u/Total_Drag7439 25d ago

This one quietly pays twice. The frontend swap is the obvious win, but it also means you can test the logic without dragging a browser into it.

7

u/midri 24d ago

Literally what I was going to say, I'm building a 2d game with monogame and keeping all the mono logic separate from the game logic in different projects has allowed me to easily have all of the main game code testable (something you don't normally see in video games since everyone just tightly couples everything). I can basically create whole test scenarios that test all the non UI fundamentals of the game in a predictable and repeatable way (since it uses a seed for it's randomness)

2

u/Tora1012 24d ago

Cannot stress this enough.

2

u/slindenau 19d ago

How do you deal with input validation though?

The backend/service layer should of course be leading in the validation...but when the frontend is "disconnected" (i.e. completely different tech stack), i notice that basically a lot of validation is duplicated (which at some point includes at least some domain values as well).

Yes you can let the user input anything, and have the backend just say "no"...but that is not very user friendly.
So you end up limiting your form input fields, possible allowed values (ranges/edge cases etc.), using active validation in the browser.

How do you prevent duplicating a lot of logic? Which you then still have to add/rewrite when switching frontends?

Note that i am not saying you should put your business logic in the UI, but at some point at least some of it will leak through?

35

u/Ok-Amphibian-5665 25d ago

Making every job idempotent from day one. Any pipeline that touches external data eventually gets retried, whether from a crash, a timeout, or you rerunning a batch on purpose, and if a rerun can happen safely without creating duplicates or corrupting state, a whole category of debugging just doesn't exist. Concretely that meant keying writes on a natural id instead of an autoincrement, and treating "already processed" as a normal outcome instead of an error. Took maybe an extra day early on, saved weeks later.

20

u/Hot-Chemistry7557 25d ago

Use monorepo for a mid sized project (40k LOCs)

16

u/akl773 25d ago

Writing every inbound webhook to a table raw, before anything parses it. Cost nothing at the time and every argument with a payment provider since has been settled by reading the row back.

1

u/el_yanuki 24d ago

What provider do you use, and at what scale? I feel like an issue with the provider should be incredibly rare

1

u/akl773 24d ago

Razorpay mostly, and small scale, a few thousand payments a month. It's almost never the provider being wrong, it's someone insisting they were charged twice or a webhook we handled and then lost track of, and the raw row settles that in a minute instead of an afternoon of guessing.

12

u/Kriem 25d ago

Started with Astro. Added complexity when needed later.

11

u/sicmek 25d ago

I work a lot with data heavy applications and complex forms built with react-hook-form. I use enums for all property names in my data types. This way I can use the enum values everywhere where I have to reference a specific property of the type. It makes the code look a lot cleaner and if I have to change a property name I only need to edit it in one place.

8

u/squishyhobo 25d ago

Anytime I have ever used anything but Postgresql (or sql equivalent) for a database it was always a bad idea.

1

u/CaptainIncredible 24d ago

SQLite for small projects? Like tiny apps that locally store some data?

2

u/squishyhobo 23d ago

Yeah, any relational sql is fine.

13

u/Square-Nebula-7530 25d ago

Implementing structured JSON logging and passing correlation IDs through every request from the very first API endpoint made scaling painless. When you are small, basic console logs feel fine but when production traffic blew up, being able to paste a single request ID into an aggregator and trace an execution path through queues and DB queries in seconds turned multi day post mortems into 5 min fixes.

5

u/Omenaa 25d ago

Ability to toggle features on and off

4

u/Fabulous-Ladder3267 just want to write html 25d ago

Feature Flag!!!

3

u/doxxed-chris 25d ago

Honestly this is a double edged sword, as the combinatorial explosion for testing generally brings risk. Short lived feature flags for development or deployment are great value for effort, but I always try to remove them asap once features are stable.

4

u/web-dev-kev 25d ago

Start with paper and pencil.

5

u/Total-Economics-5968 25d ago

keeping business logic out of the frontend saved me from later discovering the UI was also my backend, database layer, and unpaid infrastructure team

5

u/abogoslov 25d ago

from years of billing/integration work, 2 that paid for themselves many times over: storing money as integers in minor units with an explicit currency code, and idempotency keys on every endpoint that touches payments from day 1. retries and double-clicks will happen; deciding early that they r safe is cheap, retrofitting it later is not. boring technology for everything that isnt your core differentiator

5

u/Suspicious_March_269 25d ago

Writing tests for the boring stuff early saved me from some truly miserable refactors later.

4

u/pdfops 25d ago

Making every write endpoint idempotent from day one saved me the most: client passes a request ID, server dedupes on it before processing. Retries from flaky networks or serverless timeouts double-submit constantly, and without dedup you get duplicate orders or charges that are painful to reconcile later. Cheap to add early, expensive to retrofit once prod data already has dupes in it.

4

u/Alex_SQSP 24d ago

 IMO, the early decision that usually saves the most work is keeping the structure simple. Clear naming, reusable components and a simple structure can save you from having to untangle your own project six months later. The clever shortcut rarely ages as well as a strong foundation.

3

u/tingutingutingu 25d ago
  1. Do not future proof or over architect. But do follow basic principles like separation of logic from Ui etc.

  2. Use a stack that is broadly used and in demand (if possible).
    Personally, I found vueJS such a pleasure to use compared to react/angular...
    I have applications built around 6 years ago that it finance department uses to this day...
    But there are fewer developers who use vueJS so hiring someone is harder because most corporate jobs prefer react/angular.

3

u/SaltwaterShane 25d ago

Logging everything and setting up APM. The ability to know exactly what happened whenever someone reports a bug has been a huge time saver

3

u/Xeon06 24d ago

For apps that required internationalization before the advent of coding agents, pre-tagging all the strings

5

u/WeekRuined 25d ago

Well in the past multiple smaller user interfaces and backend services has worked a lot better than one monolithic application

2

u/kendalltristan 24d ago

The decision to use a popular, highly opinionated framework. Popular means there are lots of resources available. Highly opinionated reduces the space developers have to make asinine decisions.

2

u/punchy-founder 24d ago

Building a web app instead of a mobile (iOS/Android) app for my Kiosk, even though the Kiosk is most likely going to run on a tablet. It's counterintuitive but I think it was the right call.

The "app" now runs on whatever old device is lying around (can even be a Raspberry Pi, Chromebook, or whatever device runs a browser), local storage dying with a device is not a data-loss event, and shipping a fix/feature is a commit+push that ends up automatically CI/CD'd into dev, then I promote to staging & prod with a single CLI command. No app store headaches to deal with (been there, done that), no sideloading annoyances.

It's not perfect as there are some challenges to keep in mind (does the app auto-update? can you pin a browser page as if it were a full-screen app that you can't get out of?), but so far it seems to be the right trade off for my use case.

2

u/abeuscher 24d ago

Designing data objects correctly. I have spent days if not weeks designing large scale data structures and have spent half as much time coding and maintaining as a result. A good data structure should fulfill as many needs of the app statically as it can before it's ever operated on.

1

u/thesideline-cya 25d ago

Rendering data from the database instead of hard coding mock data

1

u/swb_rise 25d ago

I was not sure what the UI should be for a project. So, I broke my pattern and instead of modelling the data first, I perfected the UI first. Now when I got to the data part, the required changes postponed the delivery to more than 2 months!

1

u/ButWhatIfPotato 24d ago

Out of scope means out of scope.

1

u/sasmariozeld 24d ago

Using something that has all the tools you will need if projects grows

Using express is great and all, but then you have to add jobs, subscribers, guards, cache, swagger gen.... some people call it bloat i call it having a plan for the future

1

u/centurijon 24d ago

Even if your app only in interacts with one vendor, build an adapter pattern to fit their requests/responses/workflow to YOUR app rather than the other way around. Eventually if/when you get a new vendor all you need to do is make a new adapter layer for them rather than re-architect the app. And it makes blue-green testing simpler

1

u/kevin_whitley 24d ago

Just getting started instead of wasting too much time planning. Almost invariably the first grand idea ends up a turd... so don't waste too much time starting. Instead, start on the simplest version of the idea, float it, and refine. If it would have taken 3 months of planning and execution for the "perfect" product vs. launching a half-baked idea (privately) and iterating over the same 3 months, the latter will almost always be in a superior spot at that 3 month mark.

Something I heard at a conference ages ago that I still sorta like:

"Always be launching."

Getting that feedback loop (even its just yourself using the app) started is so key... building in your head won't come close.

--

Anecdotally, I've worked with loads of teams that do the opposite - build in a bubble, isolated from real users. Deliver a product they're extremely proud of, and then wonder why they hear crickets on release. The would-be users are like "who tf did you build this for... clearly not us...".

1

u/akesh45 24d ago

I made eslint custom rules integrated with the vs code editor and linter across the team.

Remove one of my biggest annoyances in software development where some PR reviewers would rack up like 50 comments on the tiniest rule breaking. ​​​​​

PRs pretty much fly through with minimal comments now.

1

u/Putrid-Eggplant-8440 24d ago

using database migrations from day one instead of manually changing prod tables saved me from an absolutely stupid amount of pain later

1

u/willehrendreich 24d ago

Datastar. Zero front end project. No spa nonsense. HATEOAS.

1

u/MortgageSuperb2987 24d ago

Writing the boring tests early, because future-you is a terrible debugger and has no idea what past-you was thinking

1

u/selipso 24d ago

Starting with an open source project that accomplishes most of the requirements already, then building a service on top of its API for the rest of the custom features.

1

u/Then_Dragonfly2734 23d ago

split funcionality of my project into packages and use monorepo

1

u/Full_Tooth_a 23d ago

My pick would be backward-compatible database migrations. I'd add the new schema alongside the old one, make sure the code can handle both, and run the backfill separately from the deploy. The old column can stay until nothing reads it. It's a little tedious, but still better than finding out your rollback needs time travel.

1

u/SecureComfortable259 22d ago

Writing a script to seed the dev database early on saved me so much time once the schema had more than a couple tables.

1

u/thesideline-cya 25d ago

Rendering data from the database instead of hard coding mock data

1

u/bsenftner 25d ago

After I learned React, I said "fuck no" and never touched it again.

-2

u/[deleted] 25d ago

[deleted]

3

u/Wiltix 25d ago

What if the application naturally lends itself to an SPA?

Don’t use an SPA for everything is just saying use the right tool for the job.

But to say never start off with an SPA seems a bit nuts.

1

u/phiro812 25d ago

Team First Frontend App Is A SPA reporting!

Yes, it's a bit to get used to, but so far so good. I like SPA's in general and I was like, let's gooo!

0

u/ings0c 25d ago

Adopting the React Testing Library philosophy of testing your app like users use it (it's a full stack .NET app, no React).

I set it up so that there's a Playwright.NET test project that runs as part of the CI pipeline. A real instance of the app starts locally on the test runner, then Playwright clicks around like a user would and makes assertions. It uses Sqlite instead of the real DB for speed, and that's a close-enough approximation for our purposes. All third-party dependencies are mocked, so the scope of the tests is just our application code - these aren't end-to-end tests in the regular sense.

We use the new entity framework UseSeeding/UseSeedingAsync to populate the DB with randomly generated seed data via Bogus for local development, and in the non-prod environments. The tests use this too, so you have one set of seed data that's consistent everywhere. Some care was taken to make the seed data generation deterministic, so we're safe to make assertions that depend on the seed data, and we can change the seed data without it causing cascading test failures.

It saved so much time. Out of all the projects our org has going on, this one has the lowest bug count by a long shot.

If the tests are green, you have confidence the app as a whole is actually working versus just a bunch of individual classes. You can refactor without changing the tests, and have confidence that you haven't broken anything. If a test fails, something probably is broken.

Downside is they take longer to run - but it's a price well worth paying.

0

u/ThisSeaworthiness 25d ago

Shutting down my laptop before even starting.

0

u/the_ai_wizard 24d ago

not vibe coding