DEV Community

Cover image for How to migrate your Node.js app to Deno 2.0
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

How to migrate your Node.js app to Deno 2.0

Written by Yashodhan Joshi✏️

Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements to existing features. With this release, Deno provides complete compatibility with npm modules, an extensive and stabilized standard library, and built-in functionality for several common JavaScript related tasks. All of this makes it a good time to migrate your application from Node.js to Deno and take advantage of its features.

In this article, we will see how we can migrate an existing application which uses Node.js to Deno, part-by-part.

We will see the following:

  • Why right now is a good time to migrate
  • A migration example
  • Switching from npm to Deno
  • Switching to Deno tools
  • Integrating major features from Deno
  • Advance use-case — compiling to binary

Why right now is a good time to migrate

Deno attempts to solve many of the common problems faced by developers when using Node.js. With Node, you need to install external packages for formatting and linting the code, and consequently you end up with a copy of all of your dependency in each of your project directories.

Plus, if you want to convert your code to a standalone executable, you will need another package toolchain to do that. Another common problem faced by many developers is that when trying to use TypeScript, they have to setup and configure the tsc and likely a bundler to compile from TS to plain JS.

While it can be argued that having individual and independent packages for all of these is a good thing, for a lot of developers, all of these are just intermediate steps they need to take before they can start working on their actual project code.

Deno provides solutions and built-in tools for these issues can reduce the hassle in setting up and configuring these tools.

Another reason why right now is a good point to switch is that with 2.0 release, Deno is completely compatible with Node APIs and npm packages. This means you can convert your whole setup slowly to Deno, part-by-part, instead of doing an expensive migration all at once.

And, if you find any issues, it’s easier to reverse and go back to your original setup.

Migration example

For the purpose of this article, we will use a simple project starter to demonstrate the key points. While it is definitely not as complex as a real-world project, (and as developers, we can certainly do some very complex things in our projects ), it is still a good way to show how you can plan and execute your migration.

We will be using the SvelteKit Demo template.

To set it up, run npx sv create deno-migrate-test, and select following options:

  • select Sveltekit demo as template
  • select use typescript
  • select ESLint, Prettier, and Vitest as additional dependencies
  • select the package manager of your choice; I’ve chosen npm

Once the command is done, you can cd into the directory, and run npm run dev to start the development server. You can then explore the application frontend.

Switching from npm to deno

The first and probably simplest step in this migration is to stop using npm and start using deno instead. You can use deno task <name> instead of npm run <name> to run your npm scripts, and they will be executed just like npm.

For our example, you can run deno task dev instead of npm run dev to launch the dev server like before, and check out the frontend. As you will see, the frontend works as before.

Note that if you are directly calling node from your script, such as node some-script.js, Deno will launch Node to execute that. You can also replace such occurrences with deno some-script.js to run them using Deno.

However, this will need some changes, such as changing the extension to .cjs or adding "type" : "commonjs" to the package.json file, as Deno assumes by default that .js files are module files and use es6 syntax with import / export statements.

You will also need to add the -A flag to allow all permissions to the script. If you do not want to make these changes at this stage, you can simply leave in the Node for now and change them later.

At this point you can also replace npm from your CI setup using Deno to install the dependencies with deno install and also to run the commands as above.

Once this is done, you can start using Deno in your CI and local dev, and observe for some time if you are running into any issues in day-to-day development. At this point it is very easy to roll back simply by switching back to Node/npm if you observe any problems.

Switching to Deno tools

Once you are comfortable using Deno for your local development, and it is working in your CI setup, the next step would be to replace some of the developer tools with Deno’s built-in tools. This step will require making some changes to your setup and config files, but with version control, you can easily reverse it.

The first tool to replace could be your formatter. Deno has a built-in formatter with some opinionated defaults for formatting the code. You probably have some formatter set up, such as prettier, and already have it configured to your requirements.

Deno also allows for configuring the formatter, and if you want to see how many changes it will introduce, you can run demo fmt --unstable-component --check. This will show the formatting changes that will be made to confirm with Deno’s formatter.

There are several configuration options that can be used to configure the formatter seen here, but all in all, the formatter is pretty opinionated.

My personal opinion with formatters is that as long as it can auto-format on save and can be set up easily, it is a good formatter to use. While there are several taste preferences, what matters is that everyone sticks to the same formatting rules, and the formatted code is easy to read and understand.

In this sense, while Deno might not provide as many configuration options as prettier — which provides many options to extend configurations — it is still a good formatter. You can use this if you are fine with the changes, or you can keep using your existing formatter as-is.

Similarly, you can use deno lint to replace your linter such as eslint . Similar to the formatter, this has several differences from eslint. However, this provides many common lints and provides more configuration options compared to deno fmt .

You can check them here, but they are still limited compared to ESLint rules and pre-existing configurations. If you are not satisfied, you can keep using your existing linter as well.

Finally, another tool to consider for replacement is your testing harness. In our example, it is Vitest, but you might be using Jest or Mocha for testing. This would need the most changes in your code to completely migrate to Deno.

Deno provides a built-in testing harness as well as assertion and stubbing support directly from its standard library. You can see https://jsr.io/@std/testing for more detailed information on what options are provided.

One thing to note is that Deno also supports running assertions via Chai/Mocha as well, so you might be able to change your tests from existing to Deno piece by piece.

One way to take for this could be to split your test files in two directories — one which has your existing tests, and other which has tests migrated to Deno. Then you can migrate the tests file-by-file, or even test case by test case to Deno, and while doing that you can run all of the files to maintain coverage and not let any bugs slip by.

Once you are done with the migration, you can remove the original files, whereas if you find any major problem while migrating tests, you can simply fall back to your existing tests and not use Deno for that.

Integrating major features from Deno

Once you have started using Deno for daily development and production, and you have found that you are comfortable with the switch, and there are no major problems with tooling or CI, you can start incorporating some of the big features of Deno in your codebase.

This would be a major change, and would probably require considerable changes in your code files as well. However, like other steps, there are still ways to do this slowly and in parts.

You can start by using Deno’s ability to directly compile and run TypeScript code without needing tsc or other packages. You can move your tsc compiler config from tsconfig.json to deno.json file’s compilerOptions . In our example project, the config uses extends to extend a root config.

However, Deno does not yet support that, so you will have to copy that config and merge it manually. Once that is done, you can replace the use of tsc command by directly invoking Deno on the .ts file.

If you have not used TypeScript in your project but are in interested in doing so, you can keep your existing JS files and add new functionalities in TypeScript files, as Deno allows for running both plain JS and TS without any extra configuration.

Once you are confident that the setup is working, you can also start converting your existing code to TypeScript if you want to make the codebase homogeneous.

Another place to update would be the imports and exports in your JS files. Deno by default allows import and export statements and treats the files as modules. Previously you might have added "type" : "commonjs" to the package.json or changed the extension to .cjs in order to run your JS code that used require statement.

Now you can replace the require statement with import … from '…' statements. With this, you will also need to add a prefix of node: to node standard library packages, and you can add npm: prefix for npm packages.

This would be a good point to start using jsr , Deno’s preferred package registry to import Deno-specific packages, and start using Deno’s standard library instead of Node’s.

Deno’s standard library is stabilized in the 2.0 release and provides a lot of functionality which required external packages in Node, such as working with yaml or csv files, common data structures and collections, cli module to work with cli options, and async module for operations like wait and debounce, etc.. You can simply use those function with the import statements and then use them in your code.

import { debounce } from "@std/async/debounce";
Enter fullscreen mode Exit fullscreen mode

One major feature of Deno which you can start using now is to lock up the permissions allowed to your application. While switching from Node to Deno, we used the -A flag to give all permission access to the code, just to get the ball rolling.

However, now that we are more invested in the switch, we can look at specifically which permissions our code needs, and importantly which permissions we want to allow, and only grant those.

This way, we not only understand our code’s interaction with rest of the system better, but we also prevent accidental or malicious access from the code, which might damage the developer’s system or leak potentially sensitive information.

Advanced use case: compiling to binary

One of the new features, and an advance use case of Deno, is to use it for compiling the JavaScript code into a standalone executable. This can be useful to distribute your code easily — you only need to share a single file instead of all the code and configuration, and you also don’t need the user to have Deno installed on their system.

While for frontend code in our example, this is not much usable — the final compilation provides HTML-CSS-JS files which need a server to get served. However, for cli applications, or server applications, this can be pretty useful.

The basic way to use this is to run deno compile <permissions> entry-point. This will analyze your JS files, figure out your imports, and roll them up with a JS interpreter into a single binary. This will also bundle up the node_modules with it if needed, so the size of the generated binary can be pretty hefty.

Conclusion

Deno has many great features, and with the 2.0 release, it provides complete compatibility for npm packages and the standard Node library. Along with that, it has also stabilized its own standard library, providing great many APIs with guarantee of stability and maintenance.

With the nice tools it provides, this is a good point to consider migrating your Node.js app to Deno if you are facing some pain points with Node or simply want to take advantage of Deno’s features.

That said, Node has been here for a long time and has a large ecosystem of projects, so it won’t be going away anytime soon. Thus, it makes sense to migrate your application part-by-part, even retaining some of Node.js, to use Deno’s features. With Deno’s compatibility with Node, this is a viable option to have best of both worlds.


200s only ✔️ Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.

LogRocket Node Demo

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.

Top comments (0)