npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tomkp/hetzner

v0.3.0

Published

TypeScript client for the Hetzner Cloud API

Downloads

22

Readme

hetzner

npm

A TypeScript client for the Hetzner Cloud API and Hetzner DNS API.

Installation

npm install @tomkp/hetzner

Quick Start

import { Hetzner } from "@tomkp/hetzner";

const hetzner = new Hetzner("your-api-token");

// List all servers
const { servers } = await hetzner.servers.list();
console.log(servers);

// Get a server by name
const server = await hetzner.servers.getByName("my-server");

Configuration

import { Hetzner } from "@tomkp/hetzner";

const hetzner = new Hetzner("your-api-token", {
  baseUrl: "https://api.hetzner.cloud/v1", // optional, this is the default
});

// Access the underlying client if needed
console.log(hetzner.client.baseUrl);

Using Individual API Classes

You can also use the individual API classes directly if preferred:

import { HetznerClient, ServersApi } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");
const servers = new ServersApi(client);

Usage Examples

Servers

import { HetznerClient, ServersApi, ActionsApi } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");
const servers = new ServersApi(client);
const actions = new ActionsApi(client);

// Create a server
const { server, action, root_password } = await servers.create({
  name: "my-server",
  server_type: "cx11",
  image: "ubuntu-22.04",
  location: "fsn1",
});

// Wait for the server to be ready
await actions.poll(action.id);

// Power operations
await servers.powerOff(server.id);
await servers.powerOn(server.id);
await servers.reboot(server.id);

// Delete a server
await servers.delete(server.id);

SSH Keys

import { HetznerClient, SshKeysApi } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");
const sshKeys = new SshKeysApi(client);

// Create an SSH key
const key = await sshKeys.create({
  name: "my-key",
  public_key: "ssh-rsa AAAA...",
});

// List all SSH keys
const { ssh_keys } = await sshKeys.list();

// Find by name
const myKey = await sshKeys.getByName("my-key");

Volumes

import { HetznerClient, VolumesApi, ActionsApi } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");
const volumes = new VolumesApi(client);
const actions = new ActionsApi(client);

// Create a volume
const { volume, action } = await volumes.create({
  name: "my-volume",
  size: 10, // GB
  location: "fsn1",
  format: "ext4",
});

await actions.poll(action.id);

// Attach to a server
const attachAction = await volumes.attach(volume.id, serverId);
await actions.poll(attachAction.id);

// Resize
const resizeAction = await volumes.resize(volume.id, 20);
await actions.poll(resizeAction.id);

Networks

import { HetznerClient, NetworksApi } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");
const networks = new NetworksApi(client);

// Create a network
const network = await networks.create({
  name: "my-network",
  ip_range: "10.0.0.0/16",
  subnets: [
    {
      type: "cloud",
      ip_range: "10.0.1.0/24",
      network_zone: "eu-central",
    },
  ],
});

Firewalls

import { HetznerClient, FirewallsApi } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");
const firewalls = new FirewallsApi(client);

// Create a firewall with rules
const { firewall } = await firewalls.create({
  name: "web-firewall",
  rules: [
    {
      direction: "in",
      protocol: "tcp",
      port: "80",
      source_ips: ["0.0.0.0/0", "::/0"],
    },
    {
      direction: "in",
      protocol: "tcp",
      port: "443",
      source_ips: ["0.0.0.0/0", "::/0"],
    },
  ],
});

Load Balancers

import { HetznerClient, LoadBalancersApi } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");
const loadBalancers = new LoadBalancersApi(client);

// Create a load balancer
const { load_balancer } = await loadBalancers.create({
  name: "my-lb",
  load_balancer_type: "lb11",
  location: "fsn1",
  algorithm: { type: "round_robin" },
});

// Add a target
await loadBalancers.addTarget(load_balancer.id, {
  type: "server",
  server: { id: serverId },
});

// Add a service
await loadBalancers.addService(load_balancer.id, {
  protocol: "http",
  listen_port: 80,
  destination_port: 8080,
  proxyprotocol: false,
  health_check: {
    protocol: "http",
    port: 8080,
    interval: 15,
    timeout: 10,
    retries: 3,
    http: {
      path: "/health",
    },
  },
});

Deploying an App

This example shows how to deploy a web application with server provisioning, firewall, DNS, and TLS certificates.

import { Hetzner, HetznerDns } from "@tomkp/hetzner";

const cloud = new Hetzner(process.env.HETZNER_API_TOKEN!);
const dns = new HetznerDns(process.env.HETZNER_DNS_API_TOKEN!);

const APP_NAME = "myapp";
const DOMAIN = "myapp.example.com";

// 1. Create or get SSH key
let sshKey = await cloud.sshKeys.getByName(`${APP_NAME}-key`);
if (!sshKey) {
  sshKey = await cloud.sshKeys.create({
    name: `${APP_NAME}-key`,
    public_key: process.env.SSH_PUBLIC_KEY!,
  });
}

// 2. Create firewall for web traffic
const { firewall, actions: fwActions } = await cloud.firewalls.create({
  name: `${APP_NAME}-firewall`,
  rules: [
    { direction: "in", protocol: "tcp", port: "22", source_ips: ["0.0.0.0/0", "::/0"] },
    { direction: "in", protocol: "tcp", port: "80", source_ips: ["0.0.0.0/0", "::/0"] },
    { direction: "in", protocol: "tcp", port: "443", source_ips: ["0.0.0.0/0", "::/0"] },
  ],
});
for (const action of fwActions ?? []) {
  await cloud.actions.poll(action.id);
}

// 3. Create server with SSH key and firewall
const { server, action: serverAction } = await cloud.servers.create({
  name: `${APP_NAME}-server`,
  server_type: "cx22",
  image: "ubuntu-24.04",
  location: "fsn1",
  ssh_keys: [sshKey.name],
  firewalls: [{ firewall: firewall.id }],
  labels: { app: APP_NAME },
});
await cloud.actions.poll(serverAction.id);

const serverIp = server.public_net.ipv4?.ip;
console.log(`Server ready at ${serverIp}`);

// 4. Set up DNS - create zone if needed, then add A record
let zone = await dns.zones.getByName("example.com");
if (!zone) {
  zone = await dns.zones.create({ name: "example.com", ttl: 3600 });
}

await dns.records.create({
  zone_id: zone.id,
  type: "A",
  name: "myapp", // creates myapp.example.com
  value: serverIp!,
  ttl: 300,
});

// 5. Create managed TLS certificate (auto-renewing Let's Encrypt)
const { certificate, action: certAction } = await cloud.certificates.create({
  name: `${APP_NAME}-cert`,
  type: "managed",
  domain_names: [DOMAIN],
});
if (certAction) {
  await cloud.actions.poll(certAction.id);
}

console.log(`Deployment complete!`);
console.log(`Server: ${serverIp}`);
console.log(`Domain: ${DOMAIN}`);
console.log(`Certificate: ${certificate.domain_names.join(", ")}`);

Cleanup

// Delete in reverse order of dependencies
await cloud.certificates.delete(certificate.id);
await dns.records.delete(record.id);
const deleteAction = await cloud.servers.delete(server.id);
await cloud.actions.poll(deleteAction.id);
await cloud.firewalls.delete(firewall.id);
await cloud.sshKeys.delete(sshKey.id);

Pagination

import { HetznerClient, ServersApi, paginate, fetchAllPages } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");

// Iterate through pages lazily
for await (const server of paginate(client, "/servers", "servers")) {
  console.log(server.name);
}

// Or fetch all at once
const allServers = await fetchAllPages(client, "/servers", "servers");

Error Handling

import { HetznerClient, ServersApi, HetznerError, RateLimitError } from "@tomkp/hetzner";

const client = new HetznerClient("your-api-token");
const servers = new ServersApi(client);

try {
  await servers.get(999999);
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
  } else if (error instanceof HetznerError) {
    console.log(`API error: ${error.code} - ${error.message}`);
    console.log(`Status code: ${error.statusCode}`);
  }
}

DNS API

The library also provides a client for the Hetzner DNS API. Note that the DNS API uses a separate API token from the Cloud API.

DNS Quick Start

import { HetznerDns } from "@tomkp/hetzner";

const dns = new HetznerDns("your-dns-api-token");

// List all zones
const { zones } = await dns.zones.list();
console.log(zones);

// Get a zone by name
const zone = await dns.zones.getByName("example.com");

DNS Zones

import { HetznerDns } from "@tomkp/hetzner";

const dns = new HetznerDns("your-dns-api-token");

// Create a zone
const zone = await dns.zones.create({
  name: "example.com",
  ttl: 3600,
});

// Update a zone
const updated = await dns.zones.update(zone.id, {
  ttl: 7200,
});

// Delete a zone
await dns.zones.delete(zone.id);

DNS Records

import { HetznerDns } from "@tomkp/hetzner";

const dns = new HetznerDns("your-dns-api-token");

// List records for a zone
const { records } = await dns.records.list({ zone_id: "zone-id" });

// Create an A record
const record = await dns.records.create({
  zone_id: "zone-id",
  type: "A",
  name: "www",
  value: "192.168.1.1",
  ttl: 3600,
});

// Create multiple records at once
const result = await dns.records.bulkCreate({
  records: [
    { zone_id: "zone-id", type: "A", name: "@", value: "192.168.1.1", ttl: 3600 },
    { zone_id: "zone-id", type: "A", name: "www", value: "192.168.1.1", ttl: 3600 },
    { zone_id: "zone-id", type: "MX", name: "@", value: "10 mail.example.com", ttl: 3600 },
  ],
});

// Update a record
await dns.records.update(record.id, {
  zone_id: "zone-id",
  type: "A",
  name: "www",
  value: "192.168.1.2",
  ttl: 3600,
});

// Delete a record
await dns.records.delete(record.id);

DNS Error Handling

import { HetznerDns, HetznerDnsError, DnsRateLimitError } from "@tomkp/hetzner";

const dns = new HetznerDns("your-dns-api-token");

try {
  await dns.zones.get("invalid-zone-id");
} catch (error) {
  if (error instanceof DnsRateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
  } else if (error instanceof HetznerDnsError) {
    console.log(`DNS API error: ${error.message}`);
    console.log(`Status code: ${error.statusCode}`);
  }
}

Available Resources

Cloud API

| Resource | Class | Description | |----------|-------|-------------| | Actions | ActionsApi | Track async operations | | Certificates | CertificatesApi | SSL/TLS certificates | | Datacenters | DatacentersApi | Datacenter information | | Firewalls | FirewallsApi | Network firewalls | | Floating IPs | FloatingIPsApi | Floating IP addresses | | Images | ImagesApi | OS images and snapshots | | ISOs | IsosApi | ISO images | | Load Balancer Types | LoadBalancerTypesApi | Load balancer type info | | Load Balancers | LoadBalancersApi | Load balancers | | Locations | LocationsApi | Location information | | Networks | NetworksApi | Private networks | | Placement Groups | PlacementGroupsApi | Server placement groups | | Pricing | PricingApi | Pricing information | | Primary IPs | PrimaryIPsApi | Primary IP addresses | | Server Types | ServerTypesApi | Server type information | | Servers | ServersApi | Cloud servers | | SSH Keys | SshKeysApi | SSH keys | | Volumes | VolumesApi | Block storage volumes |

DNS API

| Resource | Class | Description | |----------|-------|-------------| | Zones | ZonesApi | DNS zones | | Records | RecordsApi | DNS records |

Requirements

  • Node.js >= 22.6.0

Development

Running Tests

# Run all tests (unit + integration)
npm test

# Run only unit tests
npm run test:unit

# Run only integration tests
npm run test:integration

# Run tests with coverage
npm run test:coverage

Integration Tests

Integration tests run against the real Hetzner Cloud API. They require a valid API token:

export HETZNER_API_TOKEN=your-api-token
npm run test:integration

The integration tests include:

  • Read-only tests: Locations, datacenters, server types, pricing (no resources created)
  • Lifecycle tests: SSH keys (create, update, delete - free operations)

Tests automatically skip when no API token is provided.

License

MIT