DEV Community

Cover image for When Dinosaurs Survived the Meteor: From Python to Deno
Joshua Ghali
Joshua Ghali

Posted on

When Dinosaurs Survived the Meteor: From Python to Deno

I started seriously learning Python in 2020 while working on a release note generator for a co-op bank. It was easy to understand, enjoyable to work with, and a perfect excuse to learn something new. If you’re anything like me, you love experimenting, trying new things, and continuously improving.

From that moment, Python became my default language for almost everything. I built REST APIs with FastAPI and Flask, analyzed data with Jupyter, created CLI apps with Click—you name it. I practically ate Python for breakfast.

Funnily enough, at the time I was using TypeScript and Node.js exclusively. It wasn’t perfect for my DevOps work, but it got the job done.

Then one day, I decided to fully dive into Python. I didn’t know much about it at first, but I started learning while building the release note generator—and from that point, I was hooked.

So, what happened? Why the change then?

To be clear, I’m not saying Python is a bad language. I’ll still use it for specific cases—it’s a great tool for quickly building software.

The issues I ran into weren’t always about Python itself. Some came from the ecosystem around it, while others stemmed from the burnout I experienced trying to push Python into tasks it wasn’t originally designed for. Over time, that friction started to outweigh the joy of using the language.

Batteries not included

Python’s package management… well, it’s a bit of a mess. By default, you get pip. Install a package, and it’s installed system-wide without generating a dependency file like Node’s package.json. Want to run your app on another environment with all the right packages? Good luck—you need to remember every package and its version. Sure, pip freeze > requirements.txt exists, but it’s not automatic. Every time you add a new package, you have to run it again. And don’t even think about forgetting to activate a virtual environment—run it globally, and you’ll dump your entire system’s Python packages into requirements.txt.

You could use Poetry, pyenv, or other tools, but when I start a new project, I expect the language to come with a built-in, usable package manager. I don’t want to bolt on another tool just to get basic functionality that other ecosystems provide out of the box.

Running your app in Python isn’t much better. In Node.js, you have npm run start; in Spring, mvn spring-boot:run. Python? You either hope someone documented it in the README or added a Makefile. Neither is native, straightforward, or consistent.

And virtual environments… don’t get me started. They’re relics of the past, adding more friction than they solve. Clunky activation scripts behave differently across shells and OSes, manual management is error-prone, and reproducibility is weak. In today’s world—where containers, Poetry, and even Python’s built-in venv exist—virtualenvs feel outdated, brittle, and inefficient. They make what should be simple into a chore.

Fitting a cube in the triangle (or How I Got Burnt Out)

Shape toy kids used to play

You know that classic toy with differently shaped holes and matching blocks? Ever tried to force a cube into the triangle hole? Of course, it doesn’t fit. The obvious choice is the square hole, designed perfectly for the cube. But if you’re stubborn like me, you might twist, turn, even grab a saw to reshape the triangle—only to realize you’ve essentially recreated the square hole. You could have just used the original square hole all along.

Where am I going with this? Programming languages are similar—each shines in specific areas. Java excels at object-oriented programming, TypeScript keeps your types in check, and COBOL still rules the banking world with stable systems. My gripe isn’t with Python itself—it’s how it’s sometimes used.

Python is multi-paradigm: you can write object-oriented, procedural, functional, or hybrid code. But multi-paradigm doesn’t mean perfect at everything. I once worked on a REST API with FastAPI. Initially, it was simple and easy to follow. Then came the “let’s make it more object-oriented for maintainability” phase. Suddenly, every feature required complex design patterns, turning the code into spaghetti. Understanding it became a full-time job, and changes felt like climbing a mountain. It even contributed to burnout—I started dreading Python and FastAPI altogether.

From that experience, I learned: complexity creeps in fast if your team doesn’t agree on core principles like KISS (Keep It Simple, Stupid) and DRY (Don’t Repeat Yourself). Focusing too much on “perfect” code instead of delivering value is a trap. Clients care about results, not how beautifully your software is architected.

Moral of the story: Don’t force a cube into a triangle hole. Use the right tool for the job, and save yourself the headache.

Deno: From one reptile to another

You’ve probably guessed it by now—going forward, I’ll be using Deno with TypeScript for my projects. I was already familiar with TypeScript and Node.js, but Deno was new territory for me. So, what exactly is Deno? Deno is a modern JavaScript runtime created by Ryan Dahl, the same person who built Node.js.

Its main goal is simple: provide an uncomplicated way to develop software with JavaScript and TypeScript, without many of the headaches that come with Node.js.

I’ve been working with Deno for a while now, and I’ve become a huge fan. I won’t dive into every advantage Deno has over Node.js or Python—just the ones that matter most to me.

TypeScript supported natively

No more tsconfig migraines or wasting time tweaking endless compiler options. Forget about juggling separate build and transpile steps, or wiring up an extra bundler just to get code running. And best of all, no more CommonJS vs ESM soap opera—you can finally stop googling import/export errors at 2 a.m. and just write code that works.

Batteries included

Deno comes with a suite of built-in tools: a test runner, a linter, a formatter, and a package manager. That means you can skip the usual headache of configuring each tool separately. For me, the two that really stand out are the test runner and the package manager—they make writing, running, and managing your code a breeze right out of the box.

Package manager

One of Deno’s standout features is its package manager. When you install a new package, Deno automatically adds the reference to your deno.json file—no need for a separate npm install when you move your project to another machine. Say goodbye to pip freeze chaos or juggling virtual environments just to get your app running.

Running your software is just as straightforward. Deno provides a clear and consistent way to execute your code, and you can even configure tasks directly in your deno.json file to simplify your workflow.

Here’s an example of a deno.json configuration:

{
  "imports": {
    "@hono/swagger-ui": "jsr:@hono/swagger-ui@^0.5.2",
    "@hono/zod-openapi": "npm:@hono/zod-openapi@^1.1.0",
    "hono": "jsr:@hono/hono@^4.9.5",
    "hono-openapi": "npm:hono-openapi@^0.4.8",
    "mongoose": "npm:mongoose@^8.0.0",
    "zod": "npm:zod@^4.1.5"
  },
  "tasks": {
    "start": "deno run --watch -A src/main.ts"
  },
  "compilerOptions": {
    "jsx": "precompile",
    "jsxImportSource": "hono/jsx"
  }
}
Enter fullscreen mode Exit fullscreen mode

Test runner

Deno comes with a built-in test runner, which means no more wrestling with Jest or other heavy unit test frameworks. Running tests is straightforward and hassle-free. Here’s how you do it in Deno—it really is that simple.

// idOdd_test.ts
Deno.test("isEven", () => {
  const result = (4 % 2) == 0;
  console.assert(result);
});

// And you run it like this
// deno test idOdd_test.ts
Enter fullscreen mode Exit fullscreen mode

Compatible with Node.js and npm

If you’re like me, you probably have a few favorite npm packages that have become your go-to tools—Next.js is one of mine. The good news? With Deno, you can run your favorite npm packages just like you would in Node.js.

To see this in action, let’s create a simple Express.js app:

# Project initialization
deno init dinosaur-express
cd dinosaur-express

# Install express.js
deno add npm:express
Enter fullscreen mode Exit fullscreen mode

Now we will create a main.ts file in the directory and add the following code:

// @ts-types="npm:@types/express@4.17.15"
import express from "express";

const app = express();

app.get("/", (req, res) => {
  res.send("Welcome to Dinosaur Express!");
});

app.listen(8000);
console.log(`Server is running on http://localhost:8000`);
Enter fullscreen mode Exit fullscreen mode

Finally we will add the following script in your deno.json file

{
  "scripts": {
    "dev": "deno run --allow-net --allow-env main.ts"
  }, 
  ...
}
Enter fullscreen mode Exit fullscreen mode

And run it:

deno run dev
Enter fullscreen mode Exit fullscreen mode

Here’s what you will get in the terminal.

Deno running in your terminal

And here’s what your API will return:

Results of the REST API

Python is still powerful, but for me, Deno + TypeScript feels like the right tool for the next chapter. What do you think—stick with the classics or try the new runtimes?”

“It is not the strongest of the species that survives, nor the most intelligent, but the one most adaptable to change.” — Charles Darwin


Banner is from the article annoucing Deno 1. It can be found here
Article originally published on my website here

Top comments (0)