Building Modern Web Applications with Deno: A Beginner’s Guide
Deno is a modern, secure runtime for JavaScript and TypeScript that was created by Ryan Dahl, the original creator of Node.js. Built with modern web development needs in mind, Deno provides a more secure environment, a built-in package manager, and first-class TypeScript support—all out of the box.
In this blog post, we’ll explore Deno in depth and walk through the process of building a simple web application using Deno’s standard library and technologies. Whether you’re new to backend development or a seasoned Node.js developer curious about Deno, this guide will help you get started.
Deno is a secure runtime for JavaScript and TypeScript that runs on the V8 JavaScript engine and is written in Rust. It addresses many of Node.js’s shortcomings, such as the reliance on external tooling (like npm), a chaotic module resolution system, and lack of TypeScript support.
To install Deno, run one of the following commands depending on your OS:
# For macOS brew install deno # For Windows using PowerShell irm https://deno.land/install.ps1 | iex # For Linux and macOS using Shell curl -fsSL https://deno.land/install.sh | sh
Verify installation:
deno --version
You should see output with the installed version of Deno, TypeScript, and V8.
Let’s create a basic HTTP server using Deno.
// server.ts import { serve } from "https://deno.land/[email protected]/http/server.ts"; function handler(request: Request): Response { return new Response("Hello from Deno!", { headers: { "content-type": "text/plain" }, }); } console.log("Server running on http://localhost:8000"); await serve(handler, { port: 8000 });
To run this server, use:
deno run --allow-net server.ts
The --allow-net
flag gives the script permission to access the network.
Visit http://localhost:8000
in your browser—you should see Hello from Deno!
Deno manages packages through direct URL imports rather than a package manager like npm.
import { serve } from "https://deno.land/[email protected]/http/server.ts";
While this may seem unconventional, it makes your dependencies transparent and easier to audit. For larger projects, you can centralize imports in a deps.ts
file:
// deps.ts export { serve } from "https://deno.land/[email protected]/http/server.ts";
Then in your main file:
import { serve } from "./deps.ts";
Deno comes with a built-in test runner.
// sum.ts export function sum(a: number, b: number): number { return a + b; } // sum.test.ts import { sum } from "./sum.ts"; import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts"; deno.test("sum adds two numbers", () => { assertEquals(sum(2, 3), 5); });
Run tests using:
deno test
Deno restricts access to the file system, network, and environment variables by default. This ensures your code runs in a secure sandbox.
Here are commonly used flags:
--allow-read
— File system read access--allow-write
— File system write access--allow-net
— Network access--allow-env
— Access to environment variables--allow-run
— Permission to run subprocessesThis means you have to explicitly declare what capabilities your application needs at runtime, enhancing security.
Deno’s standard library provides modules for HTTP servers, filesystem handling, hashing, datetime utilities, and more. All official modules are hosted at:
🔗 https://deno.land/std
Community modules are available on:
🔗 https://deno.land/x
Example: Using bcrypt for password hashing
import { hash } from "https://deno.land/x/bcrypt/mod.ts"; const hashedPassword = await hash("my-password"); console.log(hashedPassword);
Let’s now build a simple RESTful API for managing tasks.
// api.ts import { serve } from "https://deno.land/[email protected]/http/server.ts"; let tasks: Array<{ id: number; title: string }> = []; async function handler(req: Request): Promise<Response> { const url = new URL(req.url); if (req.method === "GET" && url.pathname === "/tasks") { return new Response(JSON.stringify(tasks), { headers: { "Content-Type": "application/json" }, }); } if (req.method === "POST" && url.pathname === "/tasks") { const body = await req.json(); const newTask = { id: Date.now(), title: body.title }; tasks.push(newTask); return new Response(JSON.stringify(newTask), { headers: { "Content-Type": "application/json" }, status: 201, }); } return new Response("Not Found", { status: 404 }); } console.log("API running on http://localhost:8080"); await serve(handler, { port: 8080 });
Run the API with:
deno run --allow-net api.ts
Test using a tool like Postman or curl
:
curl http://localhost:8080/tasks
Or POST a task:
curl -X POST http://localhost:8080/tasks -d '{"title":"Learn Deno"}' -H "Content-Type: application/json"
Deno is a powerful and modern platform that simplifies many complexities found in traditional JavaScript backends. It’s highly suitable for developers who:
While Deno is still growing and lacks the vast ecosystem of Node.js, its design principles and built-in features make it a refreshing and productive environment for building modern web applications.
Whether you're prototyping a small tool or building a robust backend service, Deno is absolutely worth exploring.
Stay tuned for upcoming posts where we’ll dive deeper into routing with third-party frameworks like Oak, database integrations, and deploying Deno apps to the cloud.
Happy coding with Deno! 🎉
👉 If you need help building your own web apps or APIs with Deno, we offer such services here.
Information