diff --git a/docs/endpoints/index.md b/docs/endpoints/index.md index 7a2125a..8f8a5d2 100644 --- a/docs/endpoints/index.md +++ b/docs/endpoints/index.md @@ -31,3 +31,19 @@ TypedRest provides a number of endpoint types modelling common REST patterns. Mo - [Streaming Collection endpoint](reactive/streaming-collection.md) - collection of entities observable as append-only stream The constructors of all endpoints except entry endpoints take a `referrer` parameter. This is uses to inherit relative URI bases and configuration such as [error handling](../error-handling/index.md) and [link handling](../link-handling/index.md). + +## Navigating Between Endpoints + +When creating child endpoints from a parent endpoint, the `referrer` parameter ensures that configuration is inherited, including serializers, error handlers, and authentication settings. This makes it easy to build endpoint hierarchies with consistent behavior. + +TypedRest supports multiple ways to link endpoints together: + +- **[Relative URIs](../link-handling/relative-uris.md)**: Hard-code the path to child resources using relative URIs. This is the simplest approach and works well for APIs with stable, predictable structures. + +- **[URI Templates](../link-handling/uri-templates.md)**: Use parameterized URI patterns that can be resolved with specific values. This is useful for dynamic navigation where paths depend on runtime data. + +- **[Link Headers](../link-handling/link-header.md)**: Extract links from HTTP Link headers provided by the server. This allows the server to guide navigation without embedding links in response bodies. + +- **[HAL Links](../link-handling/hal.md)**: Use links embedded in HAL-formatted JSON responses. This is a standard hypermedia format that combines data and navigation in a single response. + +For more details on link handling, see the [link handling documentation](../link-handling/index.md). diff --git a/docs/link-handling/.pages b/docs/link-handling/.pages new file mode 100644 index 0000000..ba40984 --- /dev/null +++ b/docs/link-handling/.pages @@ -0,0 +1,7 @@ +nav: + - index.md + - relative-uris.md + - uri-templates.md + - link-header.md + - hal.md + - serializers.md diff --git a/docs/link-handling/hal.md b/docs/link-handling/hal.md new file mode 100644 index 0000000..6651a8f --- /dev/null +++ b/docs/link-handling/hal.md @@ -0,0 +1,173 @@ +# HAL (Hypertext Application Language) + +TypedRest supports links encoded in resources using the HAL format. HAL provides a standard way to embed links and related resources directly in JSON responses. + +For details on the HAL specification, see the [HAL draft specification](https://datatracker.ietf.org/doc/html/draft-kelly-json-hal). TypedRest focuses on making HAL links easy to consume within your endpoint hierarchy. + +## HAL Content Type + +HAL uses the content type `application/hal+json` to indicate HAL-formatted responses. TypedRest automatically detects this content type and extracts links accordingly. + +A typical HAL response looks like: + +```json +{ + "_links": { + "self": { + "href": "/contacts/1337" + }, + "edit": { + "href": "/contacts/1337/edit" + }, + "next": { + "href": "/contacts/1338" + } + }, + "name": "John Smith", + "email": "john@example.com" +} +``` + +## Using HAL Links + +HAL links are accessed using the same `Link()` and `GetLinks()` methods as HTTP Link headers: + +=== "C#" + + ```csharp + var contact = new ElementEndpoint(client, relativeUri: "./contacts/1337"); + var contactData = await contact.ReadAsync(); + + // Retrieve a link from the HAL response + var editUri = contact.Link("edit"); + if (editUri != null) + { + var editEndpoint = new Endpoint(contact, editUri); + } + ``` + +=== "TypeScript" + + ```typescript + const contact = new ElementEndpoint(client, "./contacts/1337"); + const contactData = await contact.read(); + + // Retrieve a link from the HAL response + const editUri = contact.link("edit"); + if (editUri) { + const editEndpoint = new Endpoint(contact, editUri); + } + ``` + +## Multiple Links + +HAL supports multiple links with the same relation type. Use `GetLinks()` to retrieve all of them: + +=== "C#" + + ```csharp + var resource = new ElementEndpoint(client, relativeUri: "./resource"); + await resource.ReadAsync(); + + // Get all "alternate" links + var alternates = resource.GetLinks("alternate"); + foreach (var alternate in alternates) + { + Console.WriteLine($"Alternate: {alternate}"); + } + ``` + +=== "TypeScript" + + ```typescript + const resource = new ElementEndpoint(client, "./resource"); + await resource.read(); + + // Get all "alternate" links + const alternates = resource.getLinks("alternate"); + for (const alternate of alternates) { + console.log(`Alternate: ${alternate}`); + } + ``` + +In HAL, multiple links are represented as an array: + +```json +{ + "_links": { + "alternate": [ + {"href": "/resource.xml"}, + {"href": "/resource.pdf"} + ] + } +} +``` + +## Templated Links + +HAL supports templated links using URI Templates ([RFC 6570](https://tools.ietf.org/html/rfc6570)): + +```json +{ + "_links": { + "search": { + "href": "/contacts{?name,email}", + "templated": true + } + } +} +``` + +Use `LinkTemplate()` to resolve templated links: + +=== "C#" + + ```csharp + var contacts = new CollectionEndpoint(client, relativeUri: "./contacts"); + await contacts.ReadAllAsync(); + + // Resolve the templated link + var searchUri = contacts.LinkTemplate("search", new {name = "Smith"}); + ``` + +=== "TypeScript" + + ```typescript + const contacts = new CollectionEndpoint(client, "./contacts"); + await contacts.readAll(); + + // Resolve the templated link + const searchUri = contacts.linkTemplate("search", {name: "Smith"}); + ``` + +## Combining with Link Headers + +TypedRest can extract links from both HTTP Link headers and HAL `_links` objects simultaneously. By default, both sources are checked, with header links taking precedence over HAL links when both provide a link with the same relation type. + +If you need more control over link extraction priority or want to use only specific sources, you can configure the link extraction behavior: + +=== "C#" + + ```csharp + var endpoint = new Endpoint(new Uri("http://example.com/resource")); + + // Use AggregateLinkExtractor to combine multiple extractors + endpoint.LinkExtractor = new AggregateLinkExtractor( + new HeaderLinkExtractor(), + new HalLinkExtractor() + ); + ``` + +=== "TypeScript" + + ```typescript + const endpoint = new Endpoint(new URL("http://example.com/resource")); + + // Use AggregateLinkExtractor to combine multiple extractors + endpoint.linkExtractor = new AggregateLinkExtractor( + new HeaderLinkExtractor(), + new HalLinkExtractor() + ); + ``` + +The `AggregateLinkExtractor` queries each extractor in order and returns the first match found, allowing you to control precedence by ordering the extractors. diff --git a/docs/link-handling/index.md b/docs/link-handling/index.md index 0561d30..a1210bf 100644 --- a/docs/link-handling/index.md +++ b/docs/link-handling/index.md @@ -1,8 +1,37 @@ # Link handling -TypedRest supports a variety of different links: +TypedRest provides multiple ways to navigate between endpoints, ranging from simple hard-coded URIs to dynamic links provided by the server. This flexibility allows you to build clients that work with varying levels of API hypermedia support. -- Hard-coded relative URIs between [endpoints](../endpoints/index.md) -- Links transmitted via the HTTP Link Header -- Links encoded in resources via HAL -- URI templates +## Link Resolution Methods + +TypedRest endpoints provide several methods for resolving links: + +- **`Link(rel)`**: Retrieves a link with the specified relation type from HTTP Link headers or HAL `_links` +- **`GetLinks(rel)`**: Retrieves all links with the specified relation type (useful when multiple links share the same relation) +- **`LinkTemplate(rel, variables)`**: Resolves a URI template with the specified relation type and variables + +These methods work consistently across all link sources, allowing your code to remain the same regardless of whether links come from headers, HAL, or default configurations. + +## Link Sources + +TypedRest supports multiple sources for link information: + +### [Relative URIs](relative-uris.md) + +Hard-coded relative URIs between [endpoints](../endpoints/index.md) are the simplest way to navigate. You specify the path when creating child endpoints, and TypedRest handles URI resolution. The non-standard `./` prefix pattern ensures proper path appending. + +### [URI Templates](uri-templates.md) + +URI Templates ([RFC 6570](https://tools.ietf.org/html/rfc6570)) allow parameterized URIs. Servers can provide templates that clients resolve with specific values, enabling flexible navigation without hard-coding every possible path. + +### [Link Headers](link-header.md) + +Links transmitted via the HTTP Link Header ([RFC 8288](https://tools.ietf.org/html/rfc8288)) provide navigation information without requiring specific response body formats. TypedRest also supports a non-standard `templated=true` extension for URI templates in headers. + +### [HAL](hal.md) + +Links encoded in resources via HAL (Hypertext Application Language) embed navigation directly in JSON responses using the `application/hal+json` content type and `_links` objects. + +## Serializers + +The [serializers](serializers.md) page documents how TypedRest handles different content formats (JSON, XML, BSON) and how serializer configuration is inherited between endpoints. diff --git a/docs/link-handling/link-header.md b/docs/link-handling/link-header.md new file mode 100644 index 0000000..76ae119 --- /dev/null +++ b/docs/link-handling/link-header.md @@ -0,0 +1,129 @@ +# Link Header + +TypedRest can extract links from HTTP Link headers as specified in [RFC 8288](https://tools.ietf.org/html/rfc8288). This allows servers to provide navigation information without embedding it in response bodies. + +For details on the Link header format and semantics, see [RFC 8288](https://tools.ietf.org/html/rfc8288). + +## Link Header Format + +Link headers follow this basic format: + +```http +Link: ; rel="next", + ; rel="prev" +``` + +Each link consists of: + +- A URI reference in angle brackets (``) +- One or more parameters, most importantly `rel` for the link relation type +- Optional additional parameters like `title` + +## Extracting Links + +TypedRest automatically extracts links from HTTP responses. Use the `Link()` method to retrieve a specific link by its relation type: + +=== "C#" + + ```csharp + var contacts = new CollectionEndpoint(client, relativeUri: "./contacts"); + await contacts.ReadAllAsync(); // This performs a GET request + + // Retrieve a link from the response headers + var nextPageUri = contacts.Link("next"); + if (nextPageUri != null) + { + // Navigate to the next page + var nextPage = new CollectionEndpoint(contacts, nextPageUri); + } + ``` + +=== "TypeScript" + + ```typescript + const contacts = new CollectionEndpoint(client, "./contacts"); + await contacts.readAll(); // This performs a GET request + + // Retrieve a link from the response headers + const nextPageUri = contacts.link("next"); + if (nextPageUri) { + // Navigate to the next page + const nextPage = new CollectionEndpoint(contacts, nextPageUri); + } + ``` + +## Getting All Links + +Use `GetLinks()` to retrieve all links with a specific relation type (useful when multiple links share the same relation): + +=== "C#" + + ```csharp + var endpoint = new Endpoint(new Uri("http://example.com/resource")); + await endpoint.ReadMetaAsync(); // HEAD request to fetch headers + + // Get all links with the "alternate" relation + var alternateLinks = endpoint.GetLinks("alternate"); + foreach (var link in alternateLinks) + { + Console.WriteLine($"Alternate format: {link}"); + } + ``` + +=== "TypeScript" + + ```typescript + const endpoint = new Endpoint(new URL("http://example.com/resource")); + await endpoint.readMeta(); // HEAD request to fetch headers + + // Get all links with the "alternate" relation + const alternateLinks = endpoint.getLinks("alternate"); + for (const link of alternateLinks) { + console.log(`Alternate format: ${link}`); + } + ``` + +## Templated Links in Headers + +TypedRest implements a non-standard extension: `templated=true` parameter for URI templates in Link headers. + +```http +Link: ; rel="search"; templated=true +``` + +This combines the Link header mechanism with URI template support. While not part of RFC 8288, this extension provides a convenient way to express templated links without requiring HAL or another hypermedia format. + +!!! warning + The `templated=true` extension is not standard and may not be recognized by other HTTP clients or generic Link header parsers. If interoperability with non-TypedRest clients is important, consider using HAL for templated links instead. + +When `templated=true` is present, use `LinkTemplate()` instead of `Link()`: + +=== "C#" + + ```csharp + var contacts = new CollectionEndpoint(client, relativeUri: "./contacts"); + await contacts.ReadAllAsync(); + + // Resolve the templated link + var searchUri = contacts.LinkTemplate("search", new {name = "Smith"}); + ``` + +=== "TypeScript" + + ```typescript + const contacts = new CollectionEndpoint(client, "./contacts"); + await contacts.readAll(); + + // Resolve the templated link + const searchUri = contacts.linkTemplate("search", {name: "Smith"}); + ``` + +## Supported Link Header Attributes + +TypedRest recognizes the following Link header parameters: + +- **`rel`**: The link relation type (required) +- **`title`**: A human-readable title for the link (optional, currently extracted but not exposed) +- **`templated`**: Non-standard extension indicating the URI is a template (optional) + +Other parameters defined in RFC 8288 may be present in headers but are not currently processed by TypedRest. diff --git a/docs/link-handling/relative-uris.md b/docs/link-handling/relative-uris.md new file mode 100644 index 0000000..3c22894 --- /dev/null +++ b/docs/link-handling/relative-uris.md @@ -0,0 +1,111 @@ +# Relative URIs + +TypedRest supports hard-coded relative URIs for navigating between endpoints. This is the simplest and most common way to link endpoints together. + +## Basic Usage + +When creating a child endpoint, you can specify a relative URI that will be resolved against the parent endpoint's URI: + +=== "C#" + + ```csharp + var client = new EntryEndpoint(new Uri("http://example.com/")); + var contacts = new CollectionEndpoint(client, relativeUri: "./contacts"); + // contacts.Uri will be http://example.com/contacts + ``` + +=== "TypeScript" + + ```typescript + const client = new EntryEndpoint(new URL("http://example.com/")); + const contacts = new CollectionEndpoint(client, "./contacts"); + // contacts.uri will be http://example.com/contacts + ``` + +## The `./` Prefix Pattern + +TypedRest implements a non-standard `./` prefix pattern that ensures the parent URI has a trailing slash before resolving the relative URI. This is useful when you want to append to the parent path rather than replace its last segment. + +### Behavior Without `./` Prefix + +Without the `./` prefix, standard relative URI resolution applies: + +=== "C#" + + ```csharp + var parent = new Endpoint(new Uri("http://example.com/api")); + var child = new Endpoint(parent, relativeUri: "subresource"); + // child.Uri will be http://example.com/subresource + // (replaces "api" with "subresource") + ``` + +=== "TypeScript" + + ```typescript + const parent = new Endpoint(new URL("http://example.com/api")); + const child = new Endpoint(parent, "subresource"); + // child.uri will be http://example.com/subresource + // (replaces "api" with "subresource") + ``` + +### Behavior With `./` Prefix + +With the `./` prefix, a trailing slash is added to the parent URI before resolution: + +=== "C#" + + ```csharp + var parent = new Endpoint(new Uri("http://example.com/api")); + var child = new Endpoint(parent, relativeUri: "./subresource"); + // child.Uri will be http://example.com/api/subresource + // (appends "subresource" to "api/") + ``` + +=== "TypeScript" + + ```typescript + const parent = new Endpoint(new URL("http://example.com/api")); + const child = new Endpoint(parent, "./subresource"); + // child.uri will be http://example.com/api/subresource + // (appends "subresource" to "api/") + ``` + +## When to Use the `./` Prefix + +Use the `./` prefix when: + +- You want to create a child resource under the parent's path +- The parent URI might not have a trailing slash +- You're building a hierarchical navigation structure + +Don't use the `./` prefix when: + +- You want to replace the last segment of the parent URI +- The relative URI is already properly formed for standard resolution +- You're working with absolute URIs + +## Default Links + +You can configure default links that will be used when no explicit relative URI is provided: + +=== "C#" + + ```csharp + var endpoint = new Endpoint(new Uri("http://example.com/resource")); + endpoint.SetDefaultLink("edit"); + + // Later, you can retrieve and navigate to this link + var editUri = endpoint.Link("edit"); + ``` + +=== "TypeScript" + + ```typescript + const endpoint = new Endpoint(new URL("http://example.com/resource")); + endpoint.setDefaultLink("edit", new URL("http://example.com/resource/edit")); + + // Later, you can retrieve and navigate to this link + const editUri = endpoint.link("edit"); + ``` + +Default links are useful for establishing conventions across your API client without requiring the server to provide link metadata. diff --git a/docs/link-handling/serializers.md b/docs/link-handling/serializers.md new file mode 100644 index 0000000..973e4ca --- /dev/null +++ b/docs/link-handling/serializers.md @@ -0,0 +1,185 @@ +# Serializers + +TypedRest uses serializers to convert between HTTP message bodies and typed objects. Different serializers support different formats, and availability varies between the .NET and TypeScript implementations. + +## Format Support + +| Format | .NET | TypeScript | +|--------|------|------------| +| JSON (Newtonsoft.Json) | ✅ Default | ❌ | +| JSON (System.Text.Json) | ✅ Separate NuGet | ❌ | +| JSON (Native) | ❌ | ✅ Default | +| XML | ✅ | ❌ | +| BSON | ✅ | ❌ | + +## .NET Serializers + +### JSON with Newtonsoft.Json (Default) + +The default serializer in .NET uses [Newtonsoft.Json](https://www.newtonsoft.com/json) (Json.NET): + +=== "C#" + + ```csharp + var client = new EntryEndpoint(new Uri("http://example.com/")); + // Uses NewtonsoftJsonSerializer by default + + // Configure serialization settings + client.Serializer = new NewtonsoftJsonSerializer + { + Settings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new CamelCasePropertyNamesContractResolver() + } + }; + ``` + +### JSON with System.Text.Json + +To use the newer `System.Text.Json` serializer, install the `TypedRest.SystemTextJson` NuGet package: + +```bash +dotnet add package TypedRest.SystemTextJson +``` + +!!! note + The `TypedRest.SystemTextJson` package version should match your main `TypedRest` package version. Both packages follow the same versioning scheme. + +Then configure your endpoint: + +=== "C#" + + ```csharp + var client = new EntryEndpoint(new Uri("http://example.com/")); + + // Use System.Text.Json serializer + client.Serializer = new SystemTextJsonSerializer + { + Options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + } + }; + ``` + +### XML Serializer + +The XML serializer uses .NET's built-in `XmlSerializer`: + +=== "C#" + + ```csharp + var client = new EntryEndpoint(new Uri("http://example.com/")); + + // Use XML serializer + client.Serializer = new XmlSerializer(); + ``` + +The XML serializer handles the `application/xml` and `text/xml` content types. + +### BSON Serializer + +BSON (Binary JSON) is supported for efficient binary serialization: + +=== "C#" + + ```csharp + var client = new EntryEndpoint(new Uri("http://example.com/")); + + // Use BSON serializer + client.Serializer = new BsonSerializer(); + ``` + +The BSON serializer handles the `application/bson` content type. + +## TypeScript Serializers + +### JSON (Default) + +TypeScript uses the native `JSON.parse()` and `JSON.stringify()` methods: + +=== "TypeScript" + + ```typescript + const client = new EntryEndpoint(new URL("http://example.com/")); + // Uses JsonSerializer by default + ``` + +### Custom Serializers + +You can implement custom serializers for TypeScript by implementing the serializer interface: + +=== "TypeScript" + + ```typescript + class CustomSerializer implements Serializer { + supportedMediaTypes = ["application/custom+json"]; + + serialize(data: any): string { + // Custom serialization logic + return JSON.stringify(data); + } + + deserialize(content: string): T { + // Custom deserialization logic + return JSON.parse(content) as T; + } + } + + const client = new EntryEndpoint(new URL("http://example.com/")); + client.serializer = new CustomSerializer(); + ``` + +## Custom JSON Media Types + +Both .NET and TypeScript serializers automatically handle custom JSON media types that use the `+json` suffix, such as: + +- `application/vnd.api+json` +- `application/hal+json` +- `application/problem+json` + +Any content type ending with `+json` will be processed by the JSON serializer: + +=== "C#" + + ```csharp + var client = new EntryEndpoint(new Uri("http://example.com/")); + // NewtonsoftJsonSerializer handles application/vnd.api+json automatically + ``` + +=== "TypeScript" + + ```typescript + const client = new EntryEndpoint(new URL("http://example.com/")); + // JsonSerializer handles application/vnd.api+json automatically + ``` + +## Serializer Inheritance + +When creating child endpoints, the serializer configuration is automatically inherited from the parent (referrer) endpoint: + +=== "C#" + + ```csharp + var client = new EntryEndpoint(new Uri("http://example.com/")); + client.Serializer = new SystemTextJsonSerializer(); + + // Child endpoints inherit the serializer + var contacts = new CollectionEndpoint(client, relativeUri: "./contacts"); + // contacts.Serializer is automatically set to the same SystemTextJsonSerializer + ``` + +=== "TypeScript" + + ```typescript + const client = new EntryEndpoint(new URL("http://example.com/")); + client.serializer = new CustomSerializer(); + + // Child endpoints inherit the serializer + const contacts = new CollectionEndpoint(client, "./contacts"); + // contacts.serializer is automatically set to the same CustomSerializer + ``` + +This inheritance ensures consistent serialization behavior across your entire endpoint hierarchy. diff --git a/docs/link-handling/uri-templates.md b/docs/link-handling/uri-templates.md new file mode 100644 index 0000000..322cf30 --- /dev/null +++ b/docs/link-handling/uri-templates.md @@ -0,0 +1,134 @@ +# URI Templates + +TypedRest supports [URI Templates](https://tools.ietf.org/html/rfc6570) for parameterized link construction. URI templates allow servers to provide generic patterns that clients can fill in with specific values. + +For details on the URI template syntax, see [RFC 6570](https://tools.ietf.org/html/rfc6570). TypedRest focuses on making these templates easy to use within your endpoint hierarchy. + +## Server-Provided Templates + +Templates can be provided by the server through multiple mechanisms: + +### Via Link Header + +Servers can include templated URIs in HTTP Link headers: + +```http +Link: ; rel="search"; templated=true +``` + +### Via HAL + +HAL-formatted responses can include templated links: + +```json +{ + "_links": { + "search": { + "href": "/contacts{?name,email}", + "templated": true + } + } +} +``` + +## Resolving Templates + +Use the `LinkTemplate()` method to retrieve and resolve URI templates: + +=== "C#" + + ```csharp + var contacts = new CollectionEndpoint(client, relativeUri: "./contacts"); + + // Retrieve and resolve the template + var searchUri = contacts.LinkTemplate("search", new {name = "Smith", email = "smith@example.com"}); + // searchUri will be something like /contacts?name=Smith&email=smith@example.com + ``` + +=== "TypeScript" + + ```typescript + const contacts = new CollectionEndpoint(client, "./contacts"); + + // Retrieve and resolve the template + const searchUri = contacts.linkTemplate("search", {name: "Smith", email: "smith@example.com"}); + // searchUri will be something like /contacts?name=Smith&email=smith@example.com + ``` + +## Default Link Templates + +Similar to simple links, you can set default link templates that will be used as fallbacks: + +=== "C#" + + ```csharp + var contacts = new CollectionEndpoint(client, relativeUri: "./contacts"); + contacts.SetDefaultLinkTemplate("search", "./contacts{?name,email}"); + + // Later, the template can be resolved even if the server didn't provide it + var searchUri = contacts.LinkTemplate("search", new {name = "Smith"}); + ``` + +=== "TypeScript" + + ```typescript + const contacts = new CollectionEndpoint(client, "./contacts"); + contacts.setDefaultLinkTemplate("search", "./contacts{?name,email}"); + + // Later, the template can be resolved even if the server didn't provide it + const searchUri = contacts.linkTemplate("search", {name: "Smith"}); + ``` + +## Built-In Defaults + +Some endpoint types come with built-in default link templates: + +### Collection Endpoints + +Collection endpoints automatically provide a default template for accessing elements by ID: + +=== "C#" + + ```csharp + var contacts = new CollectionEndpoint(client, relativeUri: "./contacts"); + + // This uses the built-in template "./{id}" + var contactEndpoint = contacts["1337"]; + // contactEndpoint.Uri will be http://example.com/contacts/1337 + ``` + +=== "TypeScript" + + ```typescript + const contacts = new CollectionEndpoint(client, "./contacts"); + + // This uses the built-in template "./{id}" + const contactEndpoint = contacts.get("1337"); + // contactEndpoint.uri will be http://example.com/contacts/1337 + ``` + +### Indexer Endpoints + +Indexer endpoints also use templates for addressing child resources: + +=== "C#" + + ```csharp + var items = new IndexerEndpoint(client, relativeUri: "./items"); + + // This uses the built-in template "./{id}" + var item = items["abc-123"]; + // item.Uri will be http://example.com/items/abc-123 + ``` + +=== "TypeScript" + + ```typescript + const items = new IndexerEndpoint(client, "./items"); + + // This uses the built-in template "./{id}" + const item = items.get("abc-123"); + // item.uri will be http://example.com/items/abc-123 + ``` + +These built-in templates can be overridden by server-provided templates or custom default templates.