A minimal, working TypeScript client for MikroTik RouterOS API. Supports TCP and SSL connections, old login method, and incremental command execution.
- Connects to RouterOS via TCP (8728) or SSL (8729)
- Supports both old and new login methods
- Functional factory-style API with result pattern
- Handles word encoding/decoding for RouterOS API
- Sends commands and parses responses into objects
- Incrementally extendable with custom helpers
- Generic
runCommandmethod for any RouterOS API command - Event handling for
error,close, andend
pnpm add ts-router-osts-router-os exposes a small synchronous/async API. Every async operation returns a Result object so callers decide how to branch. No operational exceptions are thrown for expected failures (auth, timeouts, traps) instead errors are handled and returned explicitly in as below:
const result = await client.runCommand("/system/identity/print");
if(!result.status) {
console.log('something wrong happened ->', result.message);
console.log('error ->', result.error); // actual error object
}
if(result.status) {
console.log('everything went well ->', result.data); // what router sent back
}ts-router-os provides minimal primitives to connect, authenticate, execute commands, observe events and handle results explicitly as below.
Create a client bound to a single RouterOS host.
import { createRouterOSClient } from "ts-router-os";
const client = createRouterOSClient({ host: "123.166.88.1", ssl: false, timeout: 15000 });You would replace 123.166.88.1 with your router ip or remote address.
host: Router address (required)port: Override default port (defaults 8728 or 8729 ifsslis true)ssl: Enable TLS (uses port 8729 if not explicitly set)timeout: Connection attempt timeout in milliseconds
Opens the TCP/TLS socket.
const connected = await client.connect();
if (!connected.status) return console.error(connected.message);Note: Call connect before login or any command.
Authenticates using either legacy or challenge flow automatically. You put username and password for a user on your router with full access.
const auth = await client.login("admin", "password");
if (!auth.status) return console.error(auth.message);Required for most commands.
Primary primitive for issuing RouterOS API commands.
// List interfaces
const list = await client.runCommand("/interface/print");
// Add IP address (you would use your router address, not 123.XXX)
const added = await client.runCommand("/ip/address/add", { address: "123.166.1.2/24", interface: "ether1" });
// Filtered query (prefix with '?')
const filtered = await client.runCommand("/ip/address/print", { "?interface": "ether1" });On success data is RouterOSResponse[] (array of plain field maps). On failure inspect message.
-
Plain key: becomes
=key=value -
Filter key: prefix with
?(e.g."?interface")
Convenience wrapper for /system/identity/print.
const id = await client.getSystemIdentity();
if (id.status) console.log(id.data);Register handlers for lifecycle events.
client.on("error", (e) => console.error(e));
client.on("close", () => console.log("closed"));
client.on("end", () => console.log("ended"));-
error: Underlying socket/protocol issue -
close: Socket closed locally or remotely -
end: Remote end finished sending data
Synchronous operation ending the connection.
const closed = client.close();
if (!closed.status) console.error(closed.message);interface RouterOSClientOptions {
host: string;
port?: number;
ssl?: boolean;
timeout?: number;
}
type RouterOSResponse = Record<string, string>;
type Result<T> =
| { status: true; data: T }
| { status: false; message: string; error?: unknown };- Create client
- Connect
- Login
- Run one or more commands
- Close when done
Early return on any failure to keep flow clear.
import { createRouterOSClient } from "ts-router-os";
const client = createRouterOSClient({ host: "123.166.88.1", ssl: false, timeout: 15000 });
const connected = await client.connect();
if (!connected.status) return console.error(connected.message);
const auth = await client.login("kizz", "12345678");
if (!auth.status) return console.error(auth.message);
const list = await client.runCommand("/interface/print");
if (!list.status) return console.error(list.message);
console.log('retrieved list', list.data);
client.close();You can add helpers of your own:
client.getSystemIdentity = async () => {
return await client.runCommand("/system/identity/print");
};
const identity = await client.getSystemIdentity();Symptoms: { status: false, message: "Failed to connect..." }
Causes & Fixes:
- Router IP is unreachable or wrong
- Port (8728/8729) is blocked by firewall
- RouterOS API service is disabled
✅ Fix: Enable API in IP > Services and allow it in firewall rules.
Symptoms: { status: false, message: "Trap error: invalid user name or password" }
✅ Fix: Check credentials and ensure the account has API access.
- Some commands require parameters (e.g., queries need
?field=value). - Example:
const result = await client.runCommand("/ip/address/print", {
"?interface": "ether1"
});- RouterOS uses self-signed certificates by default.
- Certificates don’t need validation — the client disables strict checks.
- If
ssl: trueuse port8729. - If
ssl: falseuse port8728. - If errors packet too long, try disabling ssl
- Default ports:
8728(TCP) and8729(SSL) - Socket is kept alive automatically
- Generic
runCommandallows any RouterOS command - Responses are parsed into objects for easier consumption
- Empty array response means no rows / no matches.
- Most state-changing commands require authentication.
- Filters reduce returned rows; always prefix with
?.
This library is open source, free to use and modify, and contributions are all welcome, or reach out for consultation, would be happy to help.
Author: Hussein Kizz