As a longtime .net developer, Iâve always preferred languages with strong typing. I find that I can reason about the code much better if I understand what data structures are required by a function call, and what it will return.
Naturally, this means that when doing any frontend work, I tend to reach for TypeScript. I find itâs a welcome improvement over vanilla JavaScript, especially when using typed third-party libraries.
There is only one catch: setting up typescript, esm modules, and bundling can be really painful when youâre just trying to get a simple project off the ground. Soon youâre 2 hours in, and youâre still trying to figure out why your imports arenât working.
This is where Deno comes in! Built by the creators of Node.js, deno sets out to undo the mistakes of their previous project. It has built-in support for TypeScript, esm modules, and even has a built-in bundler. This means that I can write all my code in TypeScript, and run it without any additional setup. It also does away with the node_modules
folder, which makes my hard drive very happy.
So, when starting a new project recently, I decided to give Deno a try. The project is a simple server-side rendered web application, with a bit of interactivity sprinkled in.
Itâs now been a few weeks, and Iâve been through several iterations. Here is my journey through server-side rendering in Deno.
Deno has a built-in web server??
Yes, yes it does. Using the builtin Deno.Serve()
function, you can create a simple web server in just a few lines of code.
The only downside of this approach is that youâll have to handle absolutely everything yourself. This means parsing the request, handling the response, and even serving static files. This is easy enough for a simple project, but it can quickly become unwieldy as your project grows.
Shopping for a framework
After deciding that I didnât want to write my own web server, I started looking for a lightweight framework that would handle the heavy lifting for me. I quickly found Oak, a simple framework for Deno that is very similar to Express.js.
Oak has built-in support for things like middleware and routing. It seemed like a great choice for a simple project, and itâs easy to get started with.
The next step was to figure out a way to go beyond serving API endpoints and start rendering HTML on the server. This is where things started to get interesting.
Shopping for a templating engine
Handlebars
When thinking of templating engines, my first idea was to use good old Handlebars. I had never used it before, but I remember it being all the rage a few years ago, so I figured it must be good. Right? Right??
It was⌠below my expectations. And by âbelow my expectationsâ I mean âIt makes me want to stab myselfâ. The syntax, oh the syntax!! I have nothing but compassion for anyone who has to coexist with it.
The last straw was when I had to declare a helper function to compare two values. I had to write a helper function to compare two values. In a templating engine.
ejs
After a good old web search for âhandlebars alternativesâ, I stumbled upon ejs.
As a .net developer, I was immediately reminded of Razor Pages / MVC. Simple, clean, and easy to use. I was sold.
âŚit did however come with its own set of quirks. For example, it doesnât have the equivalent of âLayoutsâ in Razor. This means that you have to manually include the header and footer in every page. It also has even weirder syntax than Razor, with various combinations of percent signs and angle brackets.
But mainly, the thing I was really missing until now was being able to unit test my UI. Not knowing if my partials were rendering correctly until I manually tested them in the browser was a pain.
In the end, I found that while the syntax was cozy and familiar, the web has moved on. I wanted something more modern, more⌠reactive. I wanted, in fact, React.
Detour to the promised land of Deno Fresh
Before I could start using React, I had to figure out how to get it to work with Deno. This is where I found đ Deno Fresh. Fresh is Denoâs answer to Next.js. With built-in support for JSX and an âislandsâ architecture that prioritizes minimal client-side JavaScript, it looked very promising.
I quickly cloned the example repo on my machine to try it out. It worked great. Sure, itâs extremely opinionated, but I can live with that.
I decided to try converting one of my ejs partials into a jsx component right inside the example project. Easy. Done. I was sold. Writing JSX instead of ejs felt natural, the way it was supposed to be all along.
At some point, however, things started to fall apart.
Testing components, for example, is not really supported out of the box. Thereâs an open issue on github and the consensus seems to be⌠letâs wait for Fresh 2.0.
As far as I understand it, the wind behind Freshâs sails is now more like a gentle breeze. It seems that the team has been busy working on Deno itself, and Fresh has been left behind.
I notice as I write this that the last official blog about it dates December 1, 2023, which is about 10 years in trendy javascript framework time.
But I like react! Maybe I can salvage this?
So it turns out that Deno Fresh uses preact-render-to-string
under the hood to transform JSX components into HTML.
With some clever refactoring, Iâm sure I can just replace my ejs Oak middleware with a React one!
-famous last words
So I did! I created a new middleware that would render my React components server-side, and it worked like a charm. If youâre curious to see what it looks like, here is a github gist with the code đ
I now had a working server-side rendered web app in Deno under my belt. I was happy with the result, even if it felt a bit of a Frankensteinâs monster.
And then I built a second project
Some time later, I wanted to start a new project. Similar requirements, so I figured I could just copy-paste the code from the first project and be done with it.
But then I thought,
If I need this, maybe others do too? Maybe I should make a library out of it? Maybe I should make my own framework? One framework to rule them all! đ
Yeah, no. No thanks Satan. Iâm good.
An important requirement of my second project was user management, authentication, and authorization.
Better-Auth looked exactly like the kind of thing I needed. Fully featured, popular, good documentation. I had absolutely no intention of trying to bend my own little framework to work with it. I just wanted to get my project done.
I also had the nagging feeling in the back of my head that I was just writing too much code for something that should be simple.
Enter Hono
The first backend framework that is mentioned in Better-Authâs documentation is Hono. I decided to take a look.
Hono does everything I was trying to build manuallyâbut better. It has built-in JSX support, a router, and great documentation. The best thing is that I donât have to support any of it myself!
It also looks almost exactly like Oak, so that was already a plus.
Unsurprisingly, building my second project was a much smoother experience.
Conclusion
This was a long journey, but a rewarding one. The nice thing was that at every point during my first project I ended up having something that was working, and that I could build upon.
Refactoring my code to use the next thing was luckily always pretty easy, and I was able to learn a lot along the way.
One thing I learned is that itâs easy to fall into the trap of doing everything âthe Deno wayâ, and assuming that a library or framework thatâs written for Deno is the best choice.
In reality, itâs important to evaluate each library or framework on its own merits, and choose the one that best fits your needs.
Frameworks like Hono that are built for multiple runtimes are obviously going to be more popular and better maintained than something thatâs built for Deno only.
The great thing about Deno is that you can reference npm modules directly (without worrying about terabytes of node_modules
), so you really have the whole JavaScript ecosystem at your disposal.
đ𼥠Key Takeaways:
- Donât assume that a library made for Deno is always the best choiceâevaluate based on actual needs.
- Multi-runtime frameworks like Hono tend to be better maintained and more flexible.
- Denoâs ability to use npm modules expands its ecosystem significantly.
- Donât be afraid to refactor and try new things â each iteration can teach you something new!