Skip to Content
Version 0.1 · ready to try

Fast, safe programs
for the back end.

Nio is a small, statically typed language. It compiles your code to one native program, finds mistakes before it runs, and comes with an HTTP server, TLS and cryptography in the box.

server.nio
import 'http';
import 'json';

type User {
    String? id;
    String name;
}

http.Response showUser(http.Request req) {
    User u = { id: req.params["id"], name: "Ada" };
    return http.json(200, json.toText(u));
}

http.Server app = http.server();
app.route(http.Method.GET, "/", http.Response (http.Request r) -> http.text(200, "hello"));
app.route(http.Method.GET, "/users/:id", http.Response (http.Request r) -> showUser(r));

await app.listen("0.0.0.0", 8080, null) catch e {
    print(`cannot listen: ${e.message}`);
};

A complete web service. nio build --release server.nio turns it into a single file you can copy to any server.

Why Nio

The things Nio is built to do well.

Native speed

Nio compiles through LLVM to one native executable. There is no virtual machine and nothing to install where it runs. Release builds optimize the whole program, runtime included.

Types that catch mistakes

Every value is checked before the program runs. Numbers never change type on their own, and a String is never null: a value that can be missing is a String?, and you must check it first.

Errors you cannot forget

The compiler works out which functions can fail. Errors go up to the caller by themselves, and you handle them with catch where it makes sense. No exceptions and no hidden jumps.

Async on one thread

async and await let one program serve many connections at once, while it waits for the network, the disk or a timer. There are no threads, so there are no locks and no data races.

Built for the back end

An HTTP server and client, TLS 1.3, cryptography, JSON, sockets, files, processes and regular expressions are all in the standard library. You need no packages to start.

Light on memory

Each value uses only its own width, so a byte[] takes one byte per element. The garbage collector gives memory back to the system when the program goes quiet.

Safe by default

TLS checks certificates unless you say not to. Regular expressions run in linear time. Packages are checked against a hash on every build and never run code when you install them.

Tools included

A formatter, a test runner, a documentation viewer, debugger support and a language server for your editor all come in the one nio command. You only need clang.

Benchmarks

The same small programs written in Nio, C, Go and Node.js, line for line, each checked to print the same result before it is timed.

≈ Con calls and mathfib, primes and mandelbrot take 0.93 to 0.98 of the time C takes.
2×faster than C at allocatingrecords: 34 ms against 72 ms with malloc and free.
10×faster async than Gochain: 23 ms, against 226 ms for Go and 54 ms for Node.js.
4.6 MBfor a busy HTTP serverAt 64 connections. Go uses 22 MB and Node.js 96 MB.

Lower is better. Wall clock, median of 9 runs.

loops100 million loop steps over an array
Nio59 ms
C22 ms
Go134 ms
Node.js137 ms
fibrecursive function calls
Nio19 ms
C20 ms
Go24 ms
Node.js80 ms
primesinteger division and comparisons
Nio65 ms
C67 ms
Go59 ms
Node.js134 ms
mandelbrotfloating-point math
Nio90 ms
C92 ms
Go94 ms
Node.js119 ms
recordsallocating 2 million records
Nio34 ms
C72 ms
Go54 ms
Node.js43 ms
stringsbuilding and comparing strings
Nio17 ms
C41 ms
Go26 ms
Node.js35 ms
filesfile I/O and regular expressions
Nio63 ms
C67 ms
Go67 ms
Node.js94 ms

Measured on Apple M4 (10 cores), macOS, clang 21, Go 1.26, Node.js 24, with the scripts in the Nio repository's benchmark folder. Nio and C are compiled with -O2. These are small programs that each stress one thing, so a real application will differ. In the HTTP test the load generator runs on the same machine as the server. The Go server uses all 10 cores, while the Nio and Node.js servers use one, so Go (1 core) is the like-for-like comparison.

Mistakes show up early

Records turn into JSON and back with one call. A field that can be missing says so in its type, and the compiler does not let you use it before you check it. A function that can fail needs no special signature: its errors go up to the caller, and catch handles them where you choose.

How errors work
config.nio
import 'fs';
import 'json';
import 'string';

type Config {
    String host;
    int port;
    String? name;    // may be missing: the compiler makes you check
}

Config load(String file) {
    byte[] raw = fs.readFile(file);    // can fail: the error goes up
    return json.parse(string.fromByteArray(raw)) as Config;
}

Config? cfg = load("config.json") catch null;
if (cfg != null) {
    print(`${cfg.host}:${cfg.port}`);    // localhost:8080
}

From source to server

The compiler is written in Nio, and clang is the only thing it needs. It runs on macOS, Linux and Windows.

  1. 1nio run app.nio

    Compile and run in one step while you work.

  2. 2nio build --release app.nio -o app

    Build an optimized native program.

  3. 3./app

    Copy the one file to a server and run it.

Try Nio today

Build the compiler from source with clang, then write your first program.