Hello World with NDL: Node.js and Deno
One of NDL’s core promises is polyglot support: the same workflow works whether you’re using Node.js, Deno, .NET, or any other runtime.
This article walks through two minimal “Hello World” demos—one with Node.js, one with Deno—to show how NDL handles different runtimes with the same declarative approach.
The key insight: the manifest only changes in one line (the command). Everything else—DNS-based service names, gateway routing, port templating—stays exactly the same.
The Node.js Version
Here’s a minimal Node.js HTTP server:
const http = require("http");
const PORT = process.env.PORT || 3000;
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello Node from NDL!\n");
});
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
Nothing special—just a standard Node.js server that reads PORT from the environment.
The Manifest
Here’s the NDL manifest that deploys it:
# Simple Node.js Hello World - NDL Demo
#
apiVersion: apps/v1alpha1
kind: Deployment
metadata:
name: hello-node
namespace: default
spec:
source:
workingDirectory: "."
command: "node server.js"
environment:
PORT: "{{.Service.Port}}"
NODE_ENV: "development"
service:
port: 3000
Key details:
command: "node server.js"— tells NDL how to start the servicePORT: "{{.Service.Port}}"— templated value from NDL’s service registry (no hardcoding)service.port: 3000— the port the service listens on (NDL uses this for health checks and routing)
Deploy and Access
# From the hello-node directory
ndl start
ndl apply -f manifest.yaml
ndl status
# Hit the service by name (no localhost:3000)
curl http://hello-node.default.ndl.test:18400
Output:
Hello Node from NDL!
Notice: you’re not calling localhost:3000. You’re calling hello-node.default.ndl.test:18400—a DNS name that goes through NDL’s gateway.
The Deno Version
Now let’s do the same thing with Deno.
Here’s the server code:
const port = parseInt(Deno.env.get("PORT") || "8000");
Deno.serve({ port }, () => {
return new Response("Hello Deno from NDL!\n", {
headers: { "content-type": "text/plain" },
});
});
console.log(`Server running at http://localhost:${port}/`);
Again, nothing unusual—just a Deno HTTP server reading PORT from the environment.
The Manifest
Here’s the NDL manifest:
# Simple Deno Hello World - NDL Demo
#
apiVersion: apps.ndl.nuewframe.io/v1alpha1
kind: Deployment
metadata:
name: hello-deno
namespace: default
spec:
source:
workingDirectory: ".."
command: "deno run --allow-net --allow-env server.ts"
environment:
PORT: "{{.Service.Port}}"
service:
port: 8000
The only difference: command: "deno run --allow-net --allow-env server.ts"
Everything else—the structure, the templating, the service definition—is identical.
Deploy and Access
# From the hello-deno directory
ndl start
ndl apply -f .ndl/manifest.yaml
ndl status
# Hit the service by name
curl http://hello-deno.default.ndl.test:18400
Output:
Hello Deno from NDL!
Same workflow. Same DNS-based routing. Same gateway. Different runtime.
What This Demonstrates
These two demos prove a few core NDL capabilities:
1. DNS-Based Service Names
Instead of localhost:3000 or localhost:8000, you access services via:
hello-node.default.ndl.test:18400hello-deno.default.ndl.test:18400
The pattern is <service>.<namespace>.ndl.test:<gateway-port>.
This means:
- No port conflicts (every service gets its own name)
- No manual port tracking
- Production-like routing (gateway → service, just like Ingress → Pod)
2. Port Templating
Both manifests use PORT: "{{.Service.Port}}" instead of hardcoding values.
NDL’s templating engine injects the correct port at runtime, so you never have to maintain parallel config files or .env files across services.
3. Polyglot Support
The same inner loop works for:
- Node.js
- Deno
- .NET
- Java
- Python
- Go
- Any runtime with a CLI
You change one line (the command) and NDL handles the rest.
4. Declarative Workflow
In both cases, the workflow is:
ndl start— start the platformndl apply -f manifest.yaml— declare your intentndl status— verify it’s runningcurl http://<service>.<namespace>.ndl.test:18400— access by name
No manual scripting. No startup order. No port hunting.
TLS Support
If you want to test HTTPS locally (e.g., for auth flows or cookies), NDL’s gateway listens on :18443 with a self-signed certificate:
# Node.js
curl -sk https://hello-node.default.ndl.test:18443
# Deno
curl -sk https://hello-deno.default.ndl.test:18443
This is useful for:
- Testing OAuth redirects
- Validating secure cookie behavior
- Simulating production-like TLS routing
Why This Matters
These demos might look trivial, but they establish something important: the inner loop is consistent across runtimes.
When you move from a “hello world” service to a multi-service distributed system, the workflow doesn’t change:
- You still declare intent in a manifest
- Services still get DNS names
- The gateway still routes traffic consistently
- Configuration still uses templating
The goal isn’t to recreate production perfectly. It’s to bring production primitives—DNS, gateways, service discovery, centralized config—into the inner loop, so local environments are repeatable and teams spend less time re-wiring the same system.
Try It Yourself
Both demos are available in the NDL repository:
Clone the repo, run the commands, and see the same workflow work for both runtimes.
Join the waitlist for early access →
From there, you can explore:
- Multi-service apps (like the .NET Todo demo)
- Database integration
- Service-to-service communication
- Centralized configuration
The promise stays the same: production primitives, locally.