All XML responses served by the media server are rendered via Edge.js templates stored in the resources/ directory. This keeps XML structure separate from TypeScript business logic — templates can be edited and the server restarted without any TypeScript compilation step.
After editing any .edge template file, simply restart the server. No TypeScript compilation is needed — Edge.js reads and renders the template files at runtime.
How templates are mounted and rendered
import edge from 'edge.js';
import path from 'node:path';
edge.mount('default', path.resolve('resources'));
Calling edge.mount('default', ...) registers the resources/ directory as the default template root. Any .edge file in that directory is then available by name (without extension):
// First argument: template name (no extension)
// Second argument: data object passed to the template
const xml = await edge.render('Browse', gallery);
Templates
description.edge — Device description
Served at GET /description.xml. Tells UPnP control points everything they need to know about this device: its identity, friendly name, and the list of services it exposes.
Variables:
| Variable | Type | Description |
|---|
uuid | string | Unique device identifier (random UUID per process) |
ip | string | Local IPv4 address of the server |
port | number | HTTP port (always 8080) |
friendlyName | string | Human-readable name shown in control point UIs |
manufacturer | string | Manufacturer name |
manufacturerURL | string | Manufacturer website URL |
The template declares three UPnP services in its <serviceList>:
| Service type | Control URL | Event URL |
|---|
ContentDirectory:1 | /upnp/control/ContentDirectory | /upnp/event/ContentDirectory |
ConnectionManager:1 | /upnp/control/ConnectionManager | /upnp/event/ConnectionManager |
AVTransport:1 | /upnp/control/AVTransport | /upnp/event/AVTransport |
Browse.edge — DIDL-Lite video list
Returned in response to a ContentDirectory Browse SOAP action. Renders a DIDL-Lite XML document listing all available video items.
Variables:
| Variable | Type | Description |
|---|
videos | object[] | Array of video items from gallery.json |
video.id | string | Unique item identifier |
video.title | string | Display title |
video.mimeType | string | MIME type (e.g. video/mp4) |
video.url | string | Playback URL for the media file |
metadata.totalVideos | number | Total number of videos in the catalog |
metadata.version | number | Catalog update ID |
Full template:
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" ...>
<s:Body>
<u:BrowseResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<Result>
@each(video in videos)
<DIDL-Lite ...>
<item id="{{ video.id }}" parentID="0" restricted="1">
<dc:title>{{ video.title }}</dc:title>
<upnp:class>object.item.videoItem</upnp:class>
<res protocolInfo="http-get:*:{{ video.mimeType }}:*">{{ video.url }}</res>
</item>
</DIDL-Lite>
@end
</Result>
<NumberReturned>{{ videos.length }}</NumberReturned>
<TotalMatches>{{ metadata.totalVideos }}</TotalMatches>
<UpdateID>{{ metadata.version }}</UpdateID>
</u:BrowseResponse>
</s:Body>
</s:Envelope>
GetSortCapabilities.edge — Sort capabilities
Returned in response to the GetSortCapabilities SOAP action. This is a static response with no template variables, returning an empty SortCapabilities element to indicate no sort criteria are imposed.
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetSortCapabilitiesResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<SortCapabilities></SortCapabilities>
</u:GetSortCapabilitiesResponse>
</s:Body>
</s:Envelope>
GetSearchCapabilities.edge — Search capabilities
Returned in response to the GetSearchCapabilities SOAP action. This is a static response with no template variables, returning * in SearchCaps to indicate all search properties are supported.
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetSearchCapabilitiesResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<SearchCaps>*</SearchCaps>
</u:GetSearchCapabilitiesResponse>
</s:Body>
</s:Envelope>
Template summary
| Template file | SOAP / HTTP trigger | Has variables? | Purpose |
|---|
description.edge | GET /description.xml | Yes | UPnP device description |
Browse.edge | Browse action | Yes | DIDL-Lite video listing |
GetSortCapabilities.edge | GetSortCapabilities action | No | Returns empty SortCapabilities (no sort constraints) |
GetSearchCapabilities.edge | GetSearchCapabilities action | No | Returns SearchCaps: * (all search properties supported) |