JS development in 2025
— 11 min read
Recently I talked to other software engineers looking to build some simple apps, and recommended RedwoodJS as a simple starting point (based on this earlier post). Since these folks haven't written production JavaScript in recent years, I realized that there were quite a few decisions to make and understand... many of which I didn't really understand myself either, despite working in JavaScript more seriously the last 5 years (and casually the 5 years prior).
So, this post is an attempt to summarize the state of JavaScript development (abbreviated JS below) going into 2025. Think of its target audience as any of these personas: a college new grad eager to jump into the industry; a vetern to programming who has been spared from needing to write JS thus far; or someone who, like me, has worked with JS for a while, but at this point feels too afraid to ask "why?"
If any of those descriptions sounds like you, read on! We'll explore the reason JS development can be summarized as a glorious and schizophrenic chaos.
Summary
- Use
nvm
to installnode
- Run
corepack enable
to allow packages to specify their package managers and node versions - Read the rest of the table below
The Table
The table is divided up into 2 layers: run the code; write the code.
The "Recommendation" column assumes you can change that decision (for example, when you're starting a new project); in reality, if you're contributing to an existing codebase, it'll have a lot of decisions made already, so all recommendations go out the window.
⬇️ NOTE: "Recommendation" options denoted by (R) are supported out-of-the-box by RedwoodJS.
Layer/Decision | "Why is this even needed?!" | Recommendation | Explanation |
---|---|---|---|
Run the code/ | |||
Backend Server | There are lots of language options for the server such as Java/Kotlin, Python, Rust, etc; even within JS, several options exist today (node, deno, bun ). We need to pick something to run the server code | Use nvm to install node at v22 | Node still has significant market share, and therefore a significant amount of community support to help you debug. For most of 2025 the long-term supported (LTS) node version will be v22 , which has enhanced performance with better TypeScript integration; expanded below |
Frontend Web Bundler | Unlike many other programming languages, JS may need to run on browsers, and source files need to be delivered there somehow, expanded below. We need to pick a tool to help do this | vite (R) | Fast build times, native support for TypeScript; see the "Dynamic vs. Static Typing" row later in this table for more on TypeScript |
Target Version | JS as a language evolved over time, and many aspects from its syntax to keywords have changed significantly. For each of the backend and frontend, We need to decide on a baseline version for this interpretation standard | ES2022 ? (R) | Anything ES6 (aka ES2015 ) and later will have the most significant and useful improvements. ES2022 balances between modern features and broad compatibility, expanded below |
Package Manager | You'll almost never build purely from scratch, and instead usually use libraries (packages) that others have published. node ships with a manager npm , but there are also alternatives like pnpm and yarn | pnpm | pnpm is mature enough, benefits from efficient disk usage, and has good dependency and workspace management |
Project Module Type | The syntax for defining and using code (both external packages and internal files from your own project) has also evolved over time. CommonJS and ES Module are two popular current choices, expanded below | ES Module if possible | New projects should benefit from the newest standard; just be aware that there are many ways to import! |
Write the code/ | |||
Dynamic vs. Static Typing | JS is dynamically typed, which allows for faster code writing than statically typed languages; static typing helps reduce errors, which speeds up development in the long run. We need to pick a side to err on | Static typing, using TypeScript at the latest version (R) | For someone new to JS, static typing will force a more explicit understanding; for everyone, the benefits (and tradeoffs) of static typing are expanded below |
Linter | Linting further reduces errors, expanded below | ESLint with typescript-eslint | Combined JS/TS linting with type-aware rules, to help new and veteran JS programmers alike reduce errors |
Formatter | Languages like Go provide default formatters like gofmt , but JS doesn't | Prettier (R) | An auto-formatter helps keep a consistent code style, so you can save energy from thinking about "correct" formatting to devote to the business logic instead. Biome is up-and-coming, but let's go with Prettier as a proven and opinionated tool for now |
Explanations Elaborated
"Glorious and schizophrenic chaos" is neither praise nor critique for the language, but more a reflection of how JS has evolved in a distributed way over the years, and an explanation to brace yourself for its tradeoffs.
- The "Glorious": JS can run everywhere! Many other languages can be transpiled to run in browsers, but JS runs natively. So it can feel glorious to build an entire app using the same language... well, mostly, as explained below.
- "Schizophrenic" is an important reminder that working with fullstack JS means writing in two languages, one for the frontend and one for the backend. They just happen to have the same file extensions and use the same syntax!
- Finally, "Chaos" reflects the various configurations and decisions that arise from its rapid development over time.
The rest of the post will attempt to help you navigate through it all, and emerge with your sanity mostly intact.
Backend Server
Language choice for the backend is worth a post itself, so for brevity, we'll assume we want to use JS. While that is not necessarily the best deciding factor, having "one" language (ignoring the "target versions" consideration!) is a glorious simplification to make when starting out, before we see other factors like performance arise.
Within JS, several options exist today (node, deno, bun
), and I recommend
node
purely from its long-established usage.
Node has different versions (just like Python, Rust, etc.), and the packages you want to use may target a minimum requirement that necessitates a higher one than what your machine currently has installed... or a lower one.
So, nvm
helps quickly switch between versions. This also helps resolve
seemingly random permission issues that may arise from otherwise installing
node
globally, as explained in this StackOverflow
question.
Finally, the choice for node v22
in 2025 is simply because it's new enough to
be officially well-supported (as the current Long Time Support edition), but not
so new that there are still bugs to be ironed out.
Frontend Web Bundling
Unlike the execution of JS on the backend that has direct access to source files, frontend JS execution requires the code to somehow be delivered to users' browsers. This has evolved quite a bit over time, in both the way a programmer needs to separate their modules and how those modules will actually be assembled and delivered.
This article by Devlin Glasman on Modules and Bundling does a great job elaborating on the history over time.
The choice for vite
is due to its speed and plugin system. I've also seen
other options like webpack
and esbuild
, but vite
seems to be the current
widely preferred choice.
Target Version
JS's language specification is abbreviated ES for ECMAScript, whose namesake Wikipedia article elaborates on the history. To over-simplify a bit, we can think of these as synonymous for the purpose of the rest of this post:
- JS
- JavaScript
- ES
- ECMAScript
For a language like Python, it's both a language and an implementation: python.org defines the language specification and provides a default implementation CPython, elaborated in this Reddit thread. That allows for a more straightforward standardization process.
In contrast, JS is just a language standard, and there's no prescriptive
implementation when you (or your users) open a browser or fire up a backend
service. But over time, it inevitably had to evolve to fill in various gaps,
like how to handle async processing (callbacks -> promises -> async functions)
and what default functions exist on Array
objects.
Put another way, JS has had a distributed evolution, from different engines implementing a language specification standard, that happened simultaneously with the evolution of that standard itself.
- Runtime engines include
JavaScriptCore, SpiderMonkey, V8
, each of which are used in some browser frontends and server backends - The language specification has evolved as part of a committee called TC39. In theory, anyone can make recommendations and proposals to the committee; someone I knew from college added Array.prototype.includes!
Long story short, the choice of ES2022
is a balance between "new-enough"
language specifications, and old-enough to have enough browser support. This is
similar to the decision for the Backend Server, but can be more conservative and
prefer an even older option if your app needs to run on older browsers.
Module Type
There are two considerations here:
- Type of the module for my own project, as defined by a Node project's
package.json
- Type of the module I want to use/import
The earlier mentioned article by Devlin Glasman on Modules and Bundling is also a relevant read here that explains the difference between CommonJS and ES Modules.
The recommendation for ES Module is for the first consideration ("my own project"), since they can also easily import CommonJS modules,
The reverse was not true for a while: CommonJS projects could not easily reuse ES Modules. And today, many packages publish as ES Modules only, with many of them linking to this disclaimer. But there seems to be some good news: if you have to use CommonJS as the module system for yoru project, you may be able to take advantage of a relatively recent feature in node v22 by Joyee Cheung to use them as well.
Typing and TypeScript
Keep in mind that a required tradeoff is that Static Typing will reject some valid code by design, which may necessitate writing the code differently.
For the latest version of TypeScript: features tend to be additive, so picking whatever is the latest version should be safe. Just be sure to install at the project scope and not global scope!
Some helpful options are off by default, and need to be enabled from a
configuration file called tsconfig.json
. This
article provides a good
initial config file. I like it because it has noUncheckedIndexedAccess
enabled
by default, which passes my bar for a thoughtful starting point.
Linting
Linting is very closely related to typing. It helps maintain clean coding conventions. Similar to static typing, it will reject more buggy code at the tradeoff of also rejecting some benign code. This is usually still worth it!
Other considerations
The above considerations aren't the only decisions you may need to make. The chaos of JS development means there are even more rabbit holes you can dive into.
Here is a slightly simpler table, without the "why does this exist" column since all of them have the same reason: JS has a huge ecosystem with a lot of options!
I wrote down some current recommendations and explanations here too, but consider them "smaller" recommendations -- they may still turn out to be important considerations for your app building experience, but I expect them to be more framework- or project-driven decisions not always within your control.
Decision | 2025 Recommendation | Explanation |
---|---|---|
Client <-> Server Protocol | GraphQL (R) | Similar to using TypeScript, adding more static typing helps avoid errors. Another alternative is trpc; just keep in mind that client and server are really TWO languages, and you may need to address backwards compatibility |
IDE/Editor | VSCode | Strong TypeScript support, integrated debugging. I use vim myself, but would recommend VSCode as a much more feature-fledged IDE for someone totally new |
Frontend UI | react at v18.3 (R) | Huge ecosystem to get community help and learn from examples. v18 has been out since 2022 and v18.3 adds helpful warnings for upgrading to v19 as mentioned in the latter's upgrade guide |
Testing Framework | Storybook for UI (R), Jest for everything else | As you work further on your project, adding automated tests will help prevent regressions |
Styling | TailwindCSS (R) plus a corresponding component library like flowbite | I'm not a frontend expert, so if you aren't one either, you'll want to save time by reusing existing style libraries! |
Inspirations and References
If you enjoyed this post, please check out some of the inspirations and references that helped me write it!