惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
Microsoft Azure Blog
Microsoft Azure Blog
aimingoo的专栏
aimingoo的专栏
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
阮一峰的网络日志
阮一峰的网络日志
Martin Fowler
Martin Fowler
B
Blog
The GitHub Blog
The GitHub Blog
T
Tailwind CSS Blog
Stack Overflow Blog
Stack Overflow Blog
L
LangChain Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
DataBreaches.Net
月光博客
月光博客
人人都是产品经理
人人都是产品经理
IT之家
IT之家
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
博客园 - Franky
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
C
Check Point Blog
罗磊的独立博客

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno Build a dinosaur runner game with Deno, pt. 2 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno
Build a REST API with Express, TypeScript, and Deno | Deno
Tim Post · 2023-03-08 · via Deno

There’s a lot of really great tutorials for anyone looking to get started building a REST API with TypeScript and Express. These tutorials, as great as they are, have two downsides:

  1. They require you to install and configure TypeScript, and ship everything required to accomplish that. This can be time-consuming and a source of frustration, particularly for new developers.

  2. They don’t address the need to be proactive about boxing in untrusted code; this isn’t surprising, as the majority of tooling doesn’t support it.

Which is why we have created this tutorial. With Deno, you don’t need to configure TypeScript, so you can get up and running with minimal dependencies.

Video Thumbnail Feel free to watch the video walkthrough of this post.

If you want to skip to the code, you can do so here.

Setting Up Express and its Types

Let’s create main.ts, which will contain the logic for our API.

In this file, let’s import Express through an npm specifier.

import express, { NextFunction, Request, Response } from "npm:express@4.18.2";

This gets us express, but not the type definitions. Let’s import the type definitions by adding this comment:


import express, { NextFunction, Request, Response } from "npm:express@4.18.2";

Next, we need to define a way to interact with Express’ application interface, and we’re going to need to define a port for it to run which we’ll get from the environment:

const app = express();
const port = Number(Deno.env.get("PORT")) || 3000;

Let’s define a test route that will say hello upon receiving a GET request, which we’ll just make the default base route for now:

app.get("/", (_req, res) => {
  res.status(200).send("Hello from Deno and Express!");
});

Now we’ve built the simple logic, we just need it to listen and start serving requests! For this we’ll use .listen() as shown:

app.listen(port, () => {
  console.log(`Listening on ${port} ...`);
});

And now we’re ready to go!

Starting the Server Securely

Let’s start our server:

Deno Permissions, "Deno Requesting Permissions"

We have to pull in all kinds of code when we’re developing APIs, from geo information, AI, ad servers and whatever other inputs have to come together to produce what’s required. Sure, we don’t anticipate Express introducing vulnerabilities, but Express is just one part of the stack you’re going to need to make something.

Had it requested access to system information, high resolution timers, or access outside of the directory, it would have been a red flag. You can specify permissions in a variety of ways, including the shebang in scripts.

At this point, we have a running API service that we can query with curl:

Deno Express Hello World, "Deno Saying Hello Through Express"

We’re now certain the framework is working correctly, so we’re sure about our installation and everything else. But, it’s not really a great working environment yet, so let’s set up our deno.jsonc file to define some helper scripts:

Deno Config Showing Tasks, "Deno.jsonc in vscode"

This works similar to package.json scripts (in fact, Deno can even use package.json scripts, but a deno.jsonc is recommended), where we have one task for development, and another to start the server without watching and reloading on changes.

Seeing the output of deno task, we can confirm that we have two scripts available:

$ deno task
Available tasks:
- dev
    deno run --allow-read --allow-net --allow-env --watch main.ts
- start
    deno run --allow-read --allow-net --allow-env main.ts

We can use deno task dev and deno task start, respectively.

Adding Logging

The next thing we’re going to need is some kind of logging functionality so that we can troubleshoot our requests while we’re building them, and that’s a great introduction into the concept of middleware in Express.

Middleware is a function that can read and even modify the req and res objects. We use middleware to do everything from logging to injecting headers, or even rate limiting and checking auth. Middleware must do one of two things when it’s done:

  • It must close the connection with a response if appropriate, or
  • It must call next(), which tells Express that it’s time to pass the objects to the next middleware function

Middleware takes 3 arguments: req and res as you’d expect, and also next, which points to the next appropriate middleware function (or returns control to the handler function).

Rather than console.log() something in every handler that we write, let’s define the first middleware function to be a logger, and tell Express that we’d like to use it. In main.ts:

const reqLogger = function (req, _res, next) {
  console.info(`${req.method} request to "${req.url}" by ${req.hostname}`);
  next();
};

You can have as much middleware as you want, and organize it in a way that works for you. Just remember that the speed of your responses depends on how quickly your middleware chain hands control back to the framework. Middleware is executed in the order that the framework is informed of it.

Generating Data

So we’re now in a pretty great place to start developing. Run the ./generate_data.ts command (deno run -A ./generate_data.ts if the shebang won’t work for you), which will generate some mock user data in data_blob.json that we can safely use like any other read-only data store safely through Deno’s import type assertions:

import demoData from "./data_blob.json" with { type: "json" };

We now have access to demoData.users in our handlers, so let’s write two handlers:

  • one /users which returns the entire contents of the users object, and
  • an additional dynamic route that allows us to look up a single user by ID
app.get("/users", (_req, res) => {
  res.status(200).json(demoData.users);
});

app.get("/users/:id", (req, res) => {
  const idx = Number(req.params.id);
  for (const user of demoData.users) {
    if (user.id === idx) {
      res.status(200).json(user);
    }
  }
  res.status(400).json({ msg: "User not found" });
});

We can also clear away the hello world default route, which leaves us with a nice API starting point:


import express, { NextFunction, Request, Response } from "npm:express@4.18.2";
import demoData from "./data_blob.json" with { type: "json" };

const app = express();
const port = Number(Deno.env.get("PORT")) || 3000;

const reqLogger = function (req, _res, next) {
  console.info(`${req.method} request to "${req.url}" by ${req.hostname}`);
  next();
};

app.use(reqLogger);

app.get("/users", (_req, res) => {
  res.status(200).json(demoData.users);
});

app.get("/users/:id", (req, res) => {
  const idx = Number(req.params.id);
  for (const user of demoData.users) {
    if (user.id === idx) {
      res.status(200).json(user);
    }
  }
  res.status(400).json({ msg: "User not found" });
});

app.listen(port, () => {
  console.log(`Listening on ${port} ...`);
});

Note the Hello, world! handler on / has been removed (and isn’t present in the linked repository).

What’s next?

We have a great starting point for a REST API in under 30 lines of code. Now, you can add a POST handler using app.post(), PUT handler with app.put(), or whatever other methods you want.

In a future post, we’ll cover how we can use Deno’s test runner and benchmark tools so we’re more comfortable taking our code from a proof of concept to something we’ll trust in production. We’ll end the series after that with ways that we can deploy our project.

Stuck? Get help in our Discord!