Hacker News new | past | comments | ask | show | jobs | submit login
How I Build Web Apps As A Freelancer (timdaub.github.io)
156 points by timdaub on Jan 18, 2021 | hide | past | favorite | 79 comments



I agree with the points here.

I came from backend development, writing mostly in C++ and Python for most of my career. I've always found modern JavaScript frameworks so difficult to reason about because it feels like I'm always working through nine layers of abstraction.

Last year, I read Julia Evans's article "A little bit of plain Javascript can do a lot," and it inspired me to try to push regular JS as far as it would go in my next project. It's now 8 months later, I have thousands of users, and I'm still using plain JavaScript.[1] No babel/webpack or anything. I do use Flask to aggregate source files together, but I purposely avoid any cleverness there. Overall, I find the code much easier to debug than any of my other web apps.

One nice advantage of using only vanilla JS is that it's easier to find contributors. If you're a React project, then only React developers can contribute. But if you're a vanilla JS project, you're not bound to developers of any particular religion. Vue, React, Angular, Svelte developers can all work in a vanilla JS codebase.

[0] https://jvns.ca/blog/2020/06/19/a-little-bit-of-plain-javasc...

[1] https://github.com/mtlynch/tinypilot


I remember when my buddy was trying to get me to use the JS framework of the day. I was just floored with how much JS was required for very simple things. I couldn't help but think to myself, "This whole thing could be a couple dozen lines in a JS file."

I am sure that some people really need these massive frameworks with build chains and universal rendering. I am just not one of them. I use JS for small logic here and there, mostly form validation and a bit of dynamic content. Most web software I know fits this description. I don't know why, but web developers are obsessed with making their jobs seem more complex than it really is. It's like everyone is scared that if someone realizes they just used HTML, JS, CSS, and a small set of scripts that people would find out they weren't real programmers.


Hey mtlynch!

Glad you like the post! Your blog inspired me to get better at writing and to take blogging more seriously. So I guess I want to say thanks! Keep up the amazing work!


Oh wow, thank you! It's so nice to hear that my blog had that kind of impact.


On my own projects I always do it old style, Java/.NET SSR, pure JavaScript using the features all browser I care about support of the box.

No SPA, no babel, no tsconfig with endless configuration options.

On enterprise projects where I am just yet another cog, I let the FE team play with whatever stacks they feel like.


Don’t you lose the value of cross browser compatibility?


No, I haven't encountered that yet. All of the JavaScript APIs I use seem to work consistently across browsers.

But I should mention that I ignore support for legacy browsers. I assume users have a version of Chrome, Firefox, Edge, or Safari from the last year or two.


So you put up a message if they don’t have a recent browser?

I did a lot of JavaScript ten years ago. I guess browser compatibility has improved since then.


Nope, no message. Nobody has ever told me that they've failed to use my app due to browser issues, so I just haven't ever put mitigations in place for users who try.

It's possible that users with old browsers have tried to use my app, found that it didn't work in their legacy browser, and then just moved on without telling me.

It's also worth mentioning that my app is for server administration, so my users are more technically-savvy than average and tend to be the kind of people who see the need to use the latest browser versions.


Man, I hope I don't have to work on their code ever (edit: to clarify, I am not knocking their actual code!)

Being so against using any sort of transpilation you avoid even TypeScript and JSX? Even though Parcel gives you these things completely for free, out of the box?

I do not think this approach would scale to a team in any good way.

Also with regards to using something like unpkg URLs in built code, whilst I a not completely against using urls for dependencies (ala deno), isn't there the huge risk that without pulling dependencies in at build time, those URLs will break or become overloaded or compromised?


As I said, I freelance. So don't worry, because I'm not planning to hire you ( ͡° ͜ʖ ͡°)

> Also with regards to using something like unpkg URLs in built code, whilst I a not completely against using urls for dependencies (ala deno), isn't there the huge risk that without pulling dependencies in at build time, those URLs will break or become overloaded or compromised?

In my post, I said I use unpkg for prototypes, so I don't think it's a problem. But in any case, unpkg let's you pin a version:

unpkg.com/:package@:version/:file


No worries buddy, and I think that it's important that you enjoy using something that works for you. I just thought it prudent to point out the limited scope of certain approaches as there's a lot of people trying to push this kind of simplicty into situations where it is less appropriate (in my opinion).


I'd rather see more of a chase for simplicity these days. It is the hallmark of good project. However today the opposite is popular.


Indeed. If 40-something me could go back in time and tell 20-something me what I’d learned in my career so far, one of the first lessons would be that simplicity scales and endures like almost nothing else and is probably the most underrated virtue in software development. (Composability would be #2.)

That is not to say that complexity is never necessary. Sometimes we are trying to solve inherently complex problems. But there is a disturbing amount of accidental complexity in modern web development. To make things worse, because web development is such a huge part of the software industry now and how so many people have come into that industry, a whole generation of developers is growing up believing that the accidental complexity is necessary or even desirable.


I've been trying to move away from things like create-react-app and towards things like Parcel, but pretty most statically typed stack has some sort "compile time" typechecking that would be needless to have in the browser. Ergo, expecting the browser to do everything we want to provide a development environment is going to be a tough call. It would be lush if Firefox and Chrome supported TypeScript natively, but it would always lag behind I think.


I currently am working on a typescript + react + redux application. The number of times I have had a developer on my team tell me that a change will be hard is disturbingly high. In vanilla javascript it would be a simple sixty minute change.

I actually really like typescript but I am unaware of being able to use it without very obnoxious things like webpack being involved.


I can't wait 'till Parcel 2 is stable, but yeah with Parcel you just include it and boom, it works.

Adapting existing projects is harder though.


With React and Redux everything is hard. These are two worst mainstream libraries out there. It so bad, even Typescript can not help too much.


It really isn't, though? I think redux introduces a LOT of complexity and much prefer something like Mobx-State-Tree, but React has been a total breeze.


For me it is still surprising how much time and effort you have to take to solve problems that was actually solved years ago. But I can understand that for beginners, or people that used vanilla JS or jQuery React is a step forward. For me, it is a big step backwards.


> I do not think this approach would scale to a team in any good way.

I mean, he literally says he's a solo/freelancer. Should be pretty clear:

> I'm sure they won't work for everyone as all our contexts differ. But working solo as a freelancer, I've found that these principles contribute to me being content about what I'm doing. Hence, I was eager to share them.

Honestly, if you're doing solo/freelance work, whoever comes along next is likely to toss out whatever you've done anyway, so you may as well optimize for whatever stack works best for you.


> Honestly, if you're doing solo/freelance work, whoever comes along next is likely to toss out whatever you've done anyway, so you may as well optimize for whatever stack works best for you.

This is a fair point


Parcel doesn’t come for free. It does help, but it brings its own problems.

If you stick an await in the code it breaks it because it transpiles it to a missing library. You have to disable Babel or import a regenerator/polyfill or whatever they need nowadays… but first, you have to figure out what’s wrong.


I didn't say Parcel comes for free - for clarification, the author said they use Parcel, and I am saying that this comes for free with Parcel.


> Even though Parcel gives you these things completely for free, out of the box?

Not only is it free OOTB, but iirc you can't turn it off[1]. So OP might be using it unknowingly. 0 config means just that, for better and worse.

[1] - https://github.com/parcel-bundler/parcel/issues/1207#issueco...


I've worked on a team that was quagmired with tooling. I would love to work with this code base.


What makes you think this approach will make it harder for other developers to work on the code?

One advantage is that there are fewer technologies another developer arriving needs to know to even get started working on the code.


I mostly agree with the author on "I avoid the new and shiny." ... The post links to another named "TypeScript is weakening the JavaScript ecosystem". In that post, the author says "My context is that I've not looked into TypeScript yet. That means I have no idea how it works.". I am all for reading a good critique, but how can you adequately critique a technology if you don't know how to use it?


I think it could be a good critique. It's a good point in general (just think about Grunt, Gulp, Broccoli, webpack and the like), but I don't see TypeScript as the "new and shiny" anymore.

Although there are alternatives (Flow, for example), to me it seems like the community has picked TS as the way forward, so in my opinion TS is a very low risk technology with real benefits, so I wouldn't hesitate adding it to a project's tech stack.

It's here to stay and a big chunk of JavaScript developers enjoy using TypeScript.


Surprisingly, many get upset about this particular line while the reason I added it was simply to be honest in my argument.

As you can already infer from the headline of that post, I tried writing a statement that is about how Typescript is weakening the JavaScript ecosystem.


I don't see the argument that it's weakening the JavaScript ecosystem. If anything it enriches it as you have (generally) safer more robust code to work with.


TypeScript is reaching GHC level of configuration flags, builds using it manage to even be slower than many native toolchains, and the team seems to just keep going with crazy type systems ideas, I already lost track of them.

Really, I hope that in a couple of years browsers would just allow for WebIDL integration and that is it.


It's quite common to see a similar concern raised about contemporary versions of the Swift language design (language-nerd lunatics taking over the asylum).


Yep, and Kotlin is just getting whole the Scala refugees.


If you're using VS Code or WebStorm/IntelliJ (and doubtless any other editor which has its own internal copy of TypeScript and uses its language service) you can write JavaScript and still get most of the benefits of using TypeScript without buying into the tooling - it's my preferred way to make use of it.

e.g. for VS Code, stick a jsconfig.json in your project's root:

    {
      "compilerOptions": {
        "checkJs": true,
        "target": "es2020"
      }
    }
If you want TypeScript to know about the types in your project, or help it with stuff it can't figure out on its own you can add them using JSDoc comments:

https://www.typescriptlang.org/docs/handbook/jsdoc-supported...

The best thing is if TypeScript is being silly about your working code (its type definitions for the DOM can be particularly painful to work with), you can just ignore it :)


Personally, I have arrived at a similar set of ideas in my consulting gigs, though not the same technology stack choices.

I think it is the need to be maximally productive in the shortest amount of time. Basically, any time you spend fixing someone else's problem is time you aren't getting closer to the clients goals. It is not that the time isn't for the greater good, but the hours I have allocated to this problem are fairly tight to start with and I don't enjoy the "We are over budget. Here is why" talks.

I have reached the point where I have started using npm ls as a proxy for how much pain migrating a legacy app will end up being.


> I think it is the need to be maximally productive in the shortest amount of time. Basically, any time you spend fixing someone else's problem is time you aren't getting closer to the clients goals.

Yes, that is an important reason how I arrived at this way of working!


as a single dev, I decided to get rid of ORMs and Typescript because... if I can't figure out what I did between my comments and my Notion documents, I'm doing it really wrong...

I've realized the more time not spent on producing output in the shortest amount of time, I'm eating away at my work life balance


Love the section "I use open source to my advantage": https://timdaub.github.io/2021/01/16/web-principles/#i-use-o....

I started doing this a couple of years ago - defaulting to writing open source code unless I had a really good reason not to - and my productivity has gone through the roof as a direct result.

The way I think about this is that ideally as a software developer I should only ever have to solve any problem once. If I solve it by writing some open source code, and do it well, I'll never have to solve it again in the future.

I compare that to my previous decade+ of engineering where I would frequently find myself solving a problem that I'd already solved a couple of years earlier, but in closed-source code that I couldn't easily reuse.


> The way I think about this is that ideally as a software developer I should only ever have to solve any problem once.

Exactly! Imagine we software developer craftspeople would all come with an "open source utility belt" when someone hired us <3


I recall reading somewhere in a book about the very early days of software development that that was exactly how it worked. Programmers literally came with bits and pieces of the software they had previously written that they drew on during development. The book mentioned that all the software during that time just came with the hardware you bought for hundreds of thousands of dollars. There wasn't an industry around selling software yet. Wish I could remember what book it was...


So I'm not against these rules, but I do run an agency that builds bespoke JS Apps for folks and a few notes. My biggest feedback was to ensure you're communicating with your client and telling them why you make these types of decisions and point out some of the tradeoffs and later costs they might accrue.

1. Using some sort of typed JavaScript via Typescript or Flow makes for much more resilient and stable code. Particularly if you're developing re-usable APIs. You're doing the right thing with automated tests but missing an opportunity by avoiding a type system. We had a much fewer runtime issues once switching to a type system.

2. The above sort of necessitates a transpiler.

3. With respect to using your own code, most freelancers (I assume) are delivering code to third party teams to later maintain. Using common, shared libraries is a benefit for later developers, so this rule should be tempered with later benefits.


1. and 2. nice reasoning. I have been considering adding Typescript recently. Mainly as I sometimes encounter bugs on e.g. an options object where a property is named incorrectly. That tends to lead to hours of debugging, so it'd be reasonable to start using it.

Re: third party code: I'm not against third-party libs. But what I tend to prefer is having control over my code. This makes me faster in building and maintaining. I'm a solo freelancer. I can't afford to wait three days for a PR upstream to be merged.

Also, I like to create my own little dependency. That way I have a way to create separate and loosely coupled APIs (shareable) and control.


Of note, Typescript is a heavy lift to add to, really port, an existing codebase. Flow can be sprinkled into an existing codebase easier if that's your goal. Once you understand the way the types work (in particular Generics) they're somewhat interchangeable.

New codebases for us use Typescript, extending existing work we use Flow that can be backported in easier.


On the other side of this point, I've used typescript and converted a major project to use it and it was fairly simple. The main thing you should start with for low hanging fruit is database queries. Make sure your database query functions are typed and it'll make the rest of your code much easier to write even if the rest isn't in typescript. Start getting types into the places where the data is coming from. The source of the data.


Some commenters are missing the point. They are narrowing their mindset by focusing how it would scale in a team environment. However, this post is coming from a freelancer perspective - it's a perfectly good set of principles, even for any side project.

Too many developers start their baby projects thinking they'll be building it alongside hundreds of fellow developers and spend weeks prematurely configuring their build tooling, boilerplate, tech stack. How it'll be supported in twenty platforms, etc. You're only giving yourself more headaches for later on.

I agree with your point on using your own work. People reach for the nicest looking library but there are so many standardized web apis capable of doing what they need, with only very little modification to make it work the way they expect it to.


Thank you. I'm happy that we speak the same language!

Getting out the big guns in solo freelance is like going to the hardware store and buying the most complex and expensive drill possible. Only to arrive home full of sweat to drill a 5mm hole.

(FYI: I love expensive drills, but I don't own one)


I don't use it anymore, but I think being able to use Vue without any build step is one of its greatest assets. You can just start writing components on vanilla .js files just like in the old school days.

I mainly use Svelte now which unfortunately requires a compilation step but the benefits are so great it's worth it.

There's such a great disconnect between what front end devs need and what the W3C/TC39 keep adding.


> I avoid the new and the shiny

> (...)

> import { h, Component, render } from 'https://unpkg.com/preact?module'

Will break on breaking changes in upstream, as no version is specified? Or am I missing something?

Ed: not to mention when unpkg goes away...


Good point, I think pinning a version would make sense here.

import { h, Component, render } from 'https://unpkg.com/preact@10.5.10?module'


Slightly misleading title change here - the article was "How I Build JavaScript Apps In 2021" but here it appears as "How I Build Web Apps As A Freelancer"

Web apps and JavaScript apps are not the same thing.


Here’s my general problem with people who profess statements like these: JS, putting aside typing, project structure, etc, simply has a small standard library. At some point, you’re rewriting utility functions that you absolutely should have, instead, be pulling in from tried and tested code base. An engineer whom merely rolls all of their own solutions doesn’t seem like the kind of engineer that builds maintainable technology.

I’d rather tree shake a lodash function than roll my own. Not because I don’t know how, but merely because that code is better than mine. Period.


I have a lot of sympathy for the arguments made here.

I strongly agree with avoiding overcomplicated processes and tools. When you start spending noticeable amounts of time trying to figure out your build process or how to manage your hosting infrastructure, to pick two repeat offenders, something is wrong. The overwhelming majority of web apps simply aren’t that complicated, and building and deploying them shouldn’t be complicated either.

Like the author, I am a big fan of tools like Parcel that have one job, do it well, and mostly “just work”. Virtual hat tip to Caddy as a web server for similar reasons. I think some of the more recent build tools like Snowpack and esbuild are worth watching too. All of these tools share the common theme that they attempt to make routine jobs simple, automatic and fast, so developers can get on with developing useful software.

In some ways, I take almost the opposite view when it comes to writing code. I tend to start almost everything from a clean slate, and usually I end up using a relatively small set of libraries, carefully chosen for each project, for productivity. I rarely use a heavyweight framework, as I think the benefits they offer are much more limited than their popularity would suggest and the potential long-term costs they come with are often underestimated. I almost never use scaffolding tools, for similar reasons.

I suspect this apparent dichotomy is a bit like what you build and what you outsource in a startup. There is the core idea, which is where you’re creating value, and you want to be as flexible and productive as possible in that area. For everything else that is necessary but not the unique part of what you’re doing, you want to be involved as little as possible. Outsourcing that stuff is good, but outsourcing 80% of something and still having to do the other 20% but on someone else’s terms is often the worst position of all.


Something was bothering me about this whole thing. I think I found what it is:

1) This is an entirely self-centered approach. "These are the tools that I like, and I code the way I'm productive, with the specific knowledge I've acquired, tailored to how I best work." That's a lot of "I" in there.

The idea of programming this way really bothers me because as someone who's hired freelancers, the best interests of your clients are nowhere in your calculus. Now, if you gave them the choice between lower cost code and code that anyone else can work on, it's your client's decision. But you are making that decision for them. If your clients are non-tech people who don't understand the ramifications of each choice, then you are doing them a disservice.

Nowhere in your article do you mention documentation or making your source easily understood by anyone else, so I rest my case.

It looks like if you are ever hit by a train, your client is screwed, and you've not thought about that.

2) I know that we, as engineers, love to reinvent the wheel, but when you start implementing stuff readily available you introduce bugs and security weaknesses. Maybe you trust yourself not to do that, but do your clients know you're not using industry standard code?

Anyway, your life, your choices. I'm not sure you've been on the client side inheriting code written like this that no new programmer can understand right away. Whoever inherits your code is going to have a lot of head scratching to do, and the only saving grace is that they will have your tests to validate any changes, and they will hope that your test coverage is appropriate.

But from the point of view of your own interests, everything you wrote is very beneficial.


JSDoc doesn't require any transpilation. It runs as native JavaScript code by using comments as type-hints. I can understand not wanting to use TypeScript, kind of, but not using JSDoc as well is honestly mildly negligent, especially when simply inferring types, i.e. being more strict about type coercion, is a massive boon to productivity.


I'm on the fence. After AngularJS, where all the work I had put into creating reusable components, all of a sudden it was gone, superceded by Angular. Everything from scratch, now with Typescript. And then I was trying flutter and it was great but also, like Angular, half-assed and not there. Then I had a period of writing everything in plain JS and embedding that in SSR pages served by Go. No reactivity :( But great SEO :) Now I found quasar, the promise is write once, deliver on all platforms. I prefer Angular to Vue but it doesn't have anything like Quasar and the Material components are lacking. vuex-orm, despite being new has more functionality out the box than ngrx-data. If you go SSR with JS there is a huge performance impact vs Go (read cost in hardware). Another downside of quasar is since it's all just a Webview on mobile you can't use grpc to communicate. grpc-web uses base64 encoding on the payload, which makes it useless. You can't use nginx on the front with grpc-web. But then again I can make it work with a RESTful backend and a websocket notifier. I would love reactivity on the frontend but SEO is more important. Adsense doesn't monetize Angular sites, says they have no content. So IDK... On the backend I write monoliths. Go is powerful enough to serve about 400 million visitors on commodity hardware, a single server. I had a cluster of 3 but it was too much effort maintaining them. Every now and then something would break. Ipsec or whatever. So I got rid of it. 1 server with raid10 and daily backups. Like I wrote Go wins on the backend. The frontend wars are undecided for me yet. I'm just one guy so I'll go with quasar and vue for now, despite my love for Angular. Also Angular is Google as is Flutter. And with Google you can't be sure they won't arbitrarily just end support one day to the next because Google's CEO felt like it today. I would love if Svelte had the tools. React, I tried getting into it but since it's so popular there are also many half-assed libs. Take their material ui. Yeah it looks nice on the outside but when you're about to use it boobytraps everywhere. Maybe one time I'll make the jump from vue to react idk. Maybe one day there will be something like Quasar for react. Idk.


Excellent post, if only for the fact that its a great way to share "what works for me" at a time when most of our local JS/Web meetup groups have been put on hold due to the pandemic, or gone onto Twitch / Zoom, so its harder to have those casual "I'm really loving such-and-such" conversations now. Thanks for sharing. FWIW I also like the general keep-it-simple / YAGNI approach. I switched from TypeScript to straight up JSDoc and with the right IDE setup it works beautifully.


The aversion to build tools is strange to me, for two reasons:

1) The pain points usually come when you're configuring things from scratch. If you're always doing solo greenfield projects, you can just carry around a boilerplate project and you should be able to largely avoid messing with configuration after the first time

2) "I optimize for performance and quality" Well, doing a JS build can help a lot with asset size and therefore performance, especially compared to doing a transpile at runtime which is what it sounds like this person is doing instead


Well, I write vanila.js, place the JavaScript files into /scripts folder and the JEE, ASP.NET build does the work for me.

Zero worries about npm, node, babel, whatever is the tool of the day.

Working today, just like 15 years ago.


Babel has been the "tool of the day" for 5+ years. Node/npm for closer to 10 years. And if you version-lock your dependencies - advisable for anything you're handing off to someone else - the same config + dependency version isn't going to suddenly break for no reason down the line. It's an unfounded trope that things are otherwise.


Different strokes for different folks.


This is a fair post and fair enough for striving for simplicity.

On the TypeScript thing, if you're already using Parcel it can do all the TypeScript stuff for you with nothing extra required!


I'm grateful tools like webpack/babel exist but I find it a bit depressing we still need them, their use-case are for problems that should have been long-solved.


I'm happy that you have fpund a stack you enjoy using!


Why not use Vue-cli or react-create-app?

I also don't like to setup webpack but by doing that you learn a lot about how things work.

This is for example why I started to write my own component framework in vue. Just to learn the underling stuff. It is also open sourced ;) https://github.com/lampewebdev/lcs-components-yt


What about security? I’d like to know author’s opinion on that. Very often this job requires you doing tradeoffs between performance and security, productivity and low hanging fruits, manteinability and team or future team composition, or maybe avoid choosing the coolest and performant technology around because there might be a scarsicity of developers knowing that technology etc. etc.


As someone who spent the last weekend getting to know Svelte... And thus fighting with Snowpack, Typescript and @web/test-runner (and then ditching it entirely and setting up jest), reading this was pretty zen-like.

Gonna check to see if he has a template or boilerplate that he uses for new projects.


Might want to edit the title, this is not a Show HN.(https://news.ycombinator.com/showhn.html)


Done. Thanks for letting me know.


Interesting set of tools. I'd love to learn how it works and see the code, there has to be some true craftsmanship inside.

Something that is unknown to a wide range of today's programmers.


Even if you don't want to write Typescript, not even using TS as JS Intellisense is an odd choice, it already catches so much without requiring any build step.


Imagine inheriting that guy's codebase.


I'd be much happier to inherit this codebase than the more common kind of front-end: "We made a bunch of choices on frameworks and toolchains 7 years ago because they ticked a lot of buzzwords. To understand what's really going on, be prepared to dive into old issue trackers of deprecated versions of unpopular projects to figure out why the code is doing thing X to work around bug Y. But isn't reactive functional metaprogramming in CoffeeScript pretty awesome though?"


Hi,

"that guy" is here and reading your mean comment. Anyways, I don't think my code is that bad.


It can be very very difficult to find your way around a large JavaScript code base without types. There's a reason everyone complains about a lack of typing in ruby, python, and javascript. I loved it too at first but as I got into larger corporate code bases I found that typing was absolutely crucial for productivity. You're going to get flack for that, may as well accept it.


This is why we use Blazor now. Our domain model has over 50 different types, some with well over 300 unique properties per. In a complex use case for our application, there can be tens of thousands of total business facts to deal with at the same time. If we were still trying to define JSON API contracts and mappers to pass this amount of domain knowledge around, we would have closed up shop a few years ago.

We now have 1 strongly-typed domain model that is used directly throughout the entire application. If you want to talk about productivity, cutting out entire layers of unnecessary mapping is key to getting leverage. Our custom javascript interop amounts to approximately 150 lines of code. Needless to say, we don't worry about javascript too much. Any interaction requiring js is quickly abstracted away under some razor component, never to be worried about again.


What would you say the codebase size threshold is for when types become needed? I mostly work on medium projects I think, and never found typescript to be of any use...


I mean kudos, you have optimized as you should, for you. As a freelancer you have other concerns, but people that will maintain it will curse you (but that's what they're paid for). You have some very cool projects.


Gladly, that is how many of my own Web projects also look like.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: