@tomkp/hetzner
v0.3.0
Published
TypeScript client for the Hetzner Cloud API
Downloads
22
Maintainers
Readme
hetzner
A TypeScript client for the Hetzner Cloud API and Hetzner DNS API.
Installation
npm install @tomkp/hetznerQuick 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:coverageIntegration 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:integrationThe 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
