From a70c6b3bc67c4096431b45d9ebf7c2e52172d062 Mon Sep 17 00:00:00 2001 From: David Moore Date: Wed, 2 Apr 2025 15:54:50 +1100 Subject: [PATCH 01/13] chore: removes azure batch from batch documentation Removes references to Azure Batch from the documentation as it is no longer a supported cloud provider for Nitric Batch. --- docs/batch.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/batch.mdx b/docs/batch.mdx index cd297465d..2dcf3ec5a 100644 --- a/docs/batch.mdx +++ b/docs/batch.mdx @@ -7,14 +7,14 @@ description: Running AI & Batch workloads with Nitric Nitric provides functionality that allows you to run large-scale jobs in parallel across multiple virtual machines or compute resources. Unlike Nitric Services, which respond to real-time events (APIs, Schedules, etc.), Batch is intended to efficiently handle tasks that can be processed in batches, which means they don't need to run in real time but can be executed asynchronously. Batches can include tasks that require a lot of computing power, or access to GPU resources, such as machine learning model training, image processing, video transcoding, data processing, and data analysis. - Batches are currently in [Preview](/reference/preview-features) and are + Batch services are currently in [Preview](/reference/preview-features) and are currently only available in the following languages: JavaScript, Python, Go, and Dart, using the nitric/aws@1.14.0, nitric/gcp@1.14.0 or later. Nitric Batch is designed to be used in conjunction with Nitric Services, allowing you to run long-running, computationally intensive tasks in parallel with your real-time services. This allows you to build complex applications that can handle both real-time and batch processing workloads. -Batches are deployed to cloud services such as [AWS Batch](https://aws.amazon.com/batch/), [Azure Batch](https://azure.microsoft.com/en-au/products/batch), and [Google Cloud Batch](https://cloud.google.com/batch/docs). Nitric abstracts the underlying cloud provider, allowing you to run your batch jobs on any of the supported cloud providers without having to worry about the specifics of each provider. +Batches are deployed to cloud services such as [AWS Batch](https://aws.amazon.com/batch/) and [Google Cloud Batch](https://cloud.google.com/batch/docs). Nitric abstracts the underlying cloud provider, allowing you to run your batch jobs on any of the supported cloud providers without having to worry about the specifics of each provider. ## Enabling Batches From c59b1441663d86137bc8086625e781f07fa1d53f Mon Sep 17 00:00:00 2001 From: David Moore Date: Wed, 2 Apr 2025 15:59:33 +1100 Subject: [PATCH 02/13] chore: replaces ctx with res --- docs/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.mdx b/docs/index.mdx index 0ce770c9b..5323b549d 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -27,7 +27,7 @@ const main = api('main') main.get('/hello/:name', async ({ req, res }) => { const { name } = req.params - ctx.res.body = `Hello ${name}` + res.body = `Hello ${name}` }) ``` @@ -38,7 +38,7 @@ const main = api('main') main.get('/hello/:name', async ({ req, res }) => { const { name } = req.params - ctx.res.body = `Hello ${name}` + res.body = `Hello ${name}` }) ``` From aab78878e753d6b0752017a75adbca453069f0ae Mon Sep 17 00:00:00 2001 From: David Moore Date: Wed, 2 Apr 2025 16:01:40 +1100 Subject: [PATCH 03/13] chore: fix typo with sql support --- docs/sql.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/sql.mdx b/docs/sql.mdx index bc222200c..f90de8917 100644 --- a/docs/sql.mdx +++ b/docs/sql.mdx @@ -9,10 +9,9 @@ Nitric provides functionality for provisioning and interacting with SQL database If you're interested in the architecture, provisioning, or deployment steps, they can be found [here](/architecture/sql). - SQL databases are currently in preview and only support PostgreSql deployed to - AWS using the
- `nitric/aws` and `nitric/awstf` providers, GCP with the `nitric/azure` - provider, or Azure with the `nitric/azure` provider. + SQL databases are currently in preview and only support PostgreSQL deployed to + AWS using the `nitric/aws` and `nitric/awstf` providers, GCP using the + `nitric/gcp` provider, or Azure using the `nitric/azure` provider.
From 47939c3b34df35633d7f3d88950abc786e622715 Mon Sep 17 00:00:00 2001 From: David Moore Date: Wed, 2 Apr 2025 16:09:33 +1100 Subject: [PATCH 04/13] chore: updates documentation Updates documentation to correct typos, provide accurate links, and improve clarity. This includes: - Correcting a typo in the keyvalue documentation. - Updating an outdated link in the azure provider documentation. - Improving the gcloud install instructions. - Correcting a typo in the schedules documentation. --- docs/keyvalue.mdx | 2 +- docs/providers/pulumi/azure.mdx | 2 +- docs/providers/pulumi/gcp.mdx | 9 ++++++++- docs/schedules.mdx | 6 ++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/keyvalue.mdx b/docs/keyvalue.mdx index cd6a217ac..ca46de9a4 100644 --- a/docs/keyvalue.mdx +++ b/docs/keyvalue.mdx @@ -16,7 +16,7 @@ Key value stores act as a database which provides the functionality for efficien ### Key -A key is a unique string that acts as a references to a value. Anytime a value is updated or read from a key value store, it is done so by referencing the key. +A key is a unique string that acts as a reference to a value. Anytime a value is updated or read from a key value store, it is done so by referencing the key. ### Value diff --git a/docs/providers/pulumi/azure.mdx b/docs/providers/pulumi/azure.mdx index 18c466f80..e2900f278 100644 --- a/docs/providers/pulumi/azure.mdx +++ b/docs/providers/pulumi/azure.mdx @@ -18,7 +18,7 @@ provider: nitric/azure@latest Nitric runs services (APIs, Schedules and Topic Subscribers) on Azure Container Apps, which is unavailable in a [small number of - regions](https://azure.microsoft.com/en-au/explore/global-infrastructure/products-by-region/?products=container-apps®ions=all). + regions](https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-region/?products=container-apps®ions=all). The Azure provider is supported by the Nitric SDKs and CLI by default. However, credentials for an Azure account will be required when using the [up command](/reference/cli) from the CLI. diff --git a/docs/providers/pulumi/gcp.mdx b/docs/providers/pulumi/gcp.mdx index d48844fd5..fb0175cbd 100644 --- a/docs/providers/pulumi/gcp.mdx +++ b/docs/providers/pulumi/gcp.mdx @@ -40,7 +40,14 @@ Download & install the [latest CLI release](https://cloud.google.com/sdk/install ```bash -curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-378.0.0-linux-x86_64.tar.gz +# Download the latest version +curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz + +# Extract the archive +tar -xf google-cloud-cli-linux-x86_64.tar.gz + +# Run the install script +./google-cloud-sdk/install.sh ``` diff --git a/docs/schedules.mdx b/docs/schedules.mdx index 5db1d9223..f15abb7cc 100644 --- a/docs/schedules.mdx +++ b/docs/schedules.mdx @@ -79,7 +79,7 @@ import ( func main() { // Run every 5 minutes - nitric.NewSchedule("process-transactions").Every("5 minutues", func(ctx *schedules.Ctx) { + nitric.NewSchedule("process-transactions").Every("5 minutes", func(ctx *schedules.Ctx) { fmt.Printf("processing at %s\n", time.Now().Format(time.RFC3339)) }) // Run at 22:00 Monday through Friday. @@ -97,15 +97,13 @@ import 'package:nitric_sdk/nitric.dart'; // run every 5 minutes Nitric.schedule("process-transactions").every("5 minutes", (ctx) async { // add code to run here - print("processing transaction") - return ctx; + print("processing transaction"); }); // Run at 22:00 Monday through Friday Nitric.schedule("send-reminder").cron("0 22 * * 1-5", (ctx) async { // add code to run here print("sending reminder"); - return ctx; }); ``` From 36ef55eed39effaadae937b31a7924630d56eab0 Mon Sep 17 00:00:00 2001 From: David Moore Date: Wed, 2 Apr 2025 16:13:25 +1100 Subject: [PATCH 05/13] chore: Updates GCP API description Corrects the API description to accurately reflect that it is a GCP API, not an Azure API. --- docs/providers/pulumi/gcp.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/pulumi/gcp.mdx b/docs/providers/pulumi/gcp.mdx index fb0175cbd..2fd19ca09 100644 --- a/docs/providers/pulumi/gcp.mdx +++ b/docs/providers/pulumi/gcp.mdx @@ -134,7 +134,7 @@ apis: # Target an API by its nitric name my-api: # provide domains to be used for the api - description: An Azure API + description: A GCP API # Add CDN configuration, required for websites deployments # Available since v1.20.0 From 81bf1703d222a778b322554133a1072be0bf7344 Mon Sep 17 00:00:00 2001 From: David Moore Date: Wed, 2 Apr 2025 16:25:00 +1100 Subject: [PATCH 06/13] chore: updates documentation examples Updates documentation to improve code examples: - Adds `fmt` import to Go example. - Updates Python import path for `Nitric` resource. - Corrects Python secret version printing example. --- docs/index.mdx | 1 + docs/schedules.mdx | 2 +- docs/secrets.mdx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.mdx b/docs/index.mdx index 5323b549d..e2d0b8cbe 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -61,6 +61,7 @@ Nitric.run() // !collapse(1:5) collapsed import ( "context" + "fmt" "github.com/nitrictech/go-sdk/nitric" "github.com/nitrictech/go-sdk/nitric/apis" diff --git a/docs/schedules.mdx b/docs/schedules.mdx index f15abb7cc..d36d23ab4 100644 --- a/docs/schedules.mdx +++ b/docs/schedules.mdx @@ -50,7 +50,7 @@ schedule('send-reminder').cron('0 22 * * 1-5', async (ctx) => { ```python !! from nitric.resources import schedule -from nitric.resources import Nitric +from nitric.application import Nitric # Run every 5 minutes processor = schedule("process-transactions") diff --git a/docs/secrets.mdx b/docs/secrets.mdx index 03be49915..b3f84d6ae 100644 --- a/docs/secrets.mdx +++ b/docs/secrets.mdx @@ -132,7 +132,7 @@ api_key = secret("api-key").allow("put") latest = await api_key.put("a new secret value") # We can get the version ID of our newly stored value -latest.version +print(latest.version) Nitric.run() ``` From d23cd60b22a09c0c94460d5a97e92b7fd6e7a0ec Mon Sep 17 00:00:00 2001 From: David Moore Date: Wed, 2 Apr 2025 17:11:17 +1100 Subject: [PATCH 07/13] chore: improves documentation and error handling Improves documentation for accuracy and clarity by updating descriptions and correcting code examples. Adds error handling to code examples to enhance robustness. Refactors file copy command in custom containers documentation for correctness. --- docs/architecture/websites.mdx | 6 +++--- docs/reference/custom-containers.mdx | 2 +- docs/reference/env.mdx | 8 ++++---- docs/reference/nodejs/secrets/secret-put.mdx | 18 ++++++++++++------ .../nodejs/sql/sql-connection-string.mdx | 7 ++++++- .../nodejs/storage/bucket-file-read.mdx | 7 ++++++- .../nodejs/storage/bucket-file-write.mdx | 19 ++++++++++++++++--- docs/reference/nodejs/storage/bucket-on.mdx | 9 ++++++--- .../nodejs/websocket/websocket-on.mdx | 19 +++++++++++++++---- .../nodejs/websocket/websocket-send.mdx | 8 +++++++- 10 files changed, 76 insertions(+), 27 deletions(-) diff --git a/docs/architecture/websites.mdx b/docs/architecture/websites.mdx index c954a627f..505ba55c9 100644 --- a/docs/architecture/websites.mdx +++ b/docs/architecture/websites.mdx @@ -50,14 +50,14 @@ classDef edgeLabel line-height:2; flowchart TD Developer["Developer"] NitricUp["nitric up"] - Storage["Azure Storage"] + BlobStorage["Azure Blob Storage"] FrontDoor["Azure Front Door"] Rewrite["Azure API Management"] Developer -->|Deploy| NitricUp - NitricUp -->|Upload Assets| Storage + NitricUp -->|Upload Assets| BlobStorage NitricUp -->|Create CDN| FrontDoor - FrontDoor -->|Serve Static Files| Storage + FrontDoor -->|Serve Static Files| BlobStorage FrontDoor -->|Rewrite /api/* to API| Rewrite classDef default line-height:1; diff --git a/docs/reference/custom-containers.mdx b/docs/reference/custom-containers.mdx index b5c515605..a4711e44f 100644 --- a/docs/reference/custom-containers.mdx +++ b/docs/reference/custom-containers.mdx @@ -61,7 +61,7 @@ RUN apt-get update -y && \ RUN pip install --upgrade pip pipenv # Copy either requirements.txt or Pipfile -COPY requirements.tx[t] Pipfil[e] Pipfile.loc[k] ./ +COPY requirements.txt Pipfile Pipfile.lock ./ # Guarantee lock file if we have a Pipfile and no Pipfile.lock RUN (stat Pipfile && pipenv lock) || echo "No Pipfile found" diff --git a/docs/reference/env.mdx b/docs/reference/env.mdx index 1f1f54b99..790f04b1e 100644 --- a/docs/reference/env.mdx +++ b/docs/reference/env.mdx @@ -10,10 +10,10 @@ Nitric sets a number of environment variables to help you manage your project. T If you are running your project in the cloud, build, or the run environment, Nitric provides prefixed Environment Variables, which you can find in the table below. -| Name | Description | -| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| NITRIC_STACK_ID | The Stack ID of the project. Example: `coolkat-gcp-4c0wg0hg` | -| NITRIC_ENVIRONMENT | The Environment that the app is running on. The value can be either `cloud` (for deployed in the cloud), `run` (for stacks running in nitric run), or `build` (for stack code running during the deployment gathering phase). | +| Name | Description | +| ------------------ | ----------------------------------------------------------------------------------------------------------------- | +| NITRIC_STACK_ID | The unique identifier for your deployed stack (e.g., `coolkat-gcp-4c0wg0hg`) | +| NITRIC_ENVIRONMENT | The environment where the app is running: `cloud` (deployed), `run` (local stack), or `build` (during deployment) | ## Application Environment Variables diff --git a/docs/reference/nodejs/secrets/secret-put.mdx b/docs/reference/nodejs/secrets/secret-put.mdx index 66701f38a..4cedab1e2 100644 --- a/docs/reference/nodejs/secrets/secret-put.mdx +++ b/docs/reference/nodejs/secrets/secret-put.mdx @@ -40,7 +40,12 @@ import { secret } from '@nitric/sdk' const keyRef = secret('apiKey').allow('put') -await keyRef.put('6c3199a3-094e-4797-bfc9-9ee2a7839286') +try { + await keyRef.put('6c3199a3-094e-4797-bfc9-9ee2a7839286') + console.log('Secret value stored successfully') +} catch (error) { + console.error('Error storing secret value:', error) +} ``` ### Get the id of a new secret version @@ -52,9 +57,10 @@ import { secret } from '@nitric/sdk' const keyRef = secret('apiKey').allow('put') -const newApiKeyVersionRef = await keyRef.put( - '6c3199a3-094e-4797-bfc9-9ee2a7839286', -) - -const versionId = newApiKeyVersionRef.version +try { + const newVersion = await keyRef.put('6c3199a3-094e-4797-bfc9-9ee2a7839286') + console.log('New secret version created:', newVersion.version) +} catch (error) { + console.error('Error creating new secret version:', error) +} ``` diff --git a/docs/reference/nodejs/sql/sql-connection-string.mdx b/docs/reference/nodejs/sql/sql-connection-string.mdx index 8fbe97f1f..2b00f36dd 100644 --- a/docs/reference/nodejs/sql/sql-connection-string.mdx +++ b/docs/reference/nodejs/sql/sql-connection-string.mdx @@ -30,7 +30,12 @@ import { sql } from '@nitric/sdk' const db = sql('my-data') // Should be called at runtime, such as in a service handler -const connStr = await db.connectionString() +try { + const connStr = await db.connectionString() + console.log('Database connection string:', connStr) +} catch (error) { + console.error('Error getting connection string:', error) +} ``` ### Connect with Prisma diff --git a/docs/reference/nodejs/storage/bucket-file-read.mdx b/docs/reference/nodejs/storage/bucket-file-read.mdx index 096131cab..af37b5fa3 100644 --- a/docs/reference/nodejs/storage/bucket-file-read.mdx +++ b/docs/reference/nodejs/storage/bucket-file-read.mdx @@ -32,5 +32,10 @@ const assets = bucket('assets').allow('read') const logo = assets.file('images/logo.png') -const logoData = await logo.read() +try { + const logoData = await logo.read() + console.log('Logo data:', logoData) +} catch (error) { + console.error('Error reading logo:', error) +} ``` diff --git a/docs/reference/nodejs/storage/bucket-file-write.mdx b/docs/reference/nodejs/storage/bucket-file-write.mdx index 0d307e3be..c23aa8ef2 100644 --- a/docs/reference/nodejs/storage/bucket-file-write.mdx +++ b/docs/reference/nodejs/storage/bucket-file-write.mdx @@ -17,8 +17,16 @@ import { bucket } from '@nitric/sdk' const assets = bucket('assets').allow('write') const logo = assets.file('images/logo.png') - -await logo.write(someImageData) +const imageData = new Uint8Array([ + /* image data */ +]) + +try { + await logo.write(imageData) + console.log('Logo written successfully') +} catch (error) { + console.error('Error writing logo:', error) +} ``` ## Parameters @@ -41,5 +49,10 @@ const assets = bucket('assets').allow('write') const txt = assets.file('my-text-file.txt') const buffer = Buffer.from('My Test File...') -await txt.write(buffer) +try { + await txt.write(buffer) + console.log('Text file written successfully') +} catch (error) { + console.error('Error writing text file:', error) +} ``` diff --git a/docs/reference/nodejs/storage/bucket-on.mdx b/docs/reference/nodejs/storage/bucket-on.mdx index 4870dfab9..0f22b8730 100644 --- a/docs/reference/nodejs/storage/bucket-on.mdx +++ b/docs/reference/nodejs/storage/bucket-on.mdx @@ -29,9 +29,12 @@ assets.on('write', '/images/cat', (ctx) => { // If `on` is called with a permissioned bucket, a file will also be provided with the request accessibleAssets.on('write', '/images/dog', async (ctx) => { - const dogImage = await ctx.req.file.read() - - console.log(dogImage) + try { + const dogImage = await ctx.req.file.read() + console.log(dogImage) + } catch (error) { + console.error('Error reading dog image:', error) + } }) ``` diff --git a/docs/reference/nodejs/websocket/websocket-on.mdx b/docs/reference/nodejs/websocket/websocket-on.mdx index f954ae573..9c9a53910 100644 --- a/docs/reference/nodejs/websocket/websocket-on.mdx +++ b/docs/reference/nodejs/websocket/websocket-on.mdx @@ -78,8 +78,13 @@ const socket = websocket('socket') const connections = kv('connections').allow('get', 'set', 'delete') socket.on('connect', async (ctx) => { - const connectionList = await connections.get('connections') - await connections..set('connections', [ + let connectionList = [] + try { + connectionList = await connections.get('connections') + } catch (e) { + console.log('Creating new connections store') + } + await connections.set('connections', [ ...connectionList, { // store any metadata related to the connection here @@ -89,10 +94,16 @@ socket.on('connect', async (ctx) => { }) socket.on('disconnect', async (ctx) => { - const connectionList = await connections.get('connections') + let connectionList = [] + try { + connectionList = await connections.get('connections') + } catch (e) { + console.log('No connections found') + return + } await connections.set( 'connections', - connectionList.filter((c) => c.connectionId === ctx.req.connectionId) + connectionList.filter((c) => c.connectionId === ctx.req.connectionId), ) }) ``` diff --git a/docs/reference/nodejs/websocket/websocket-send.mdx b/docs/reference/nodejs/websocket/websocket-send.mdx index aafdd8def..db857d131 100644 --- a/docs/reference/nodejs/websocket/websocket-send.mdx +++ b/docs/reference/nodejs/websocket/websocket-send.mdx @@ -77,7 +77,13 @@ socket.on('disconnect', async (ctx) => { }) const broadcast = async (data) => { - const conns = await connections.get('connections') + let conns = {} + try { + conns = await connections.get('connections') + } catch (e) { + console.log('No connections found') + return + } await Promise.all( Object.keys(conns).map(async (connectionId) => { From 862f4345b432ccb76cfab47e3bc607a2ab30da98 Mon Sep 17 00:00:00 2001 From: Jye Cusch Date: Thu, 3 Apr 2025 11:56:17 +1100 Subject: [PATCH 08/13] docs: refresh APIs page --- docs/apis.mdx | 1334 ++++++++++--------------------------------------- 1 file changed, 252 insertions(+), 1082 deletions(-) diff --git a/docs/apis.mdx b/docs/apis.mdx index afc4d0af5..9cc8335d1 100644 --- a/docs/apis.mdx +++ b/docs/apis.mdx @@ -4,37 +4,31 @@ description: 'Building HTTP APIs with Nitric' # APIs -Nitric has built-in support for web apps and HTTP API development. The `api` resource allows you to create APIs in your applications, including routing, middleware and request handlers. +Nitric provides a simple and powerful way to build HTTP APIs in your applications. The `api` resource allows you to create APIs with routing, middleware, request handlers, and security features. -If you're interested in the architecture, provisioning, or deployment steps, they can be found [here](/architecture/apis). +## Quick Start -## Creating APIs - -Nitric allows you define named APIs, each with their own routes, middleware, handlers and security. - -Here's an example of how to create a new API with Nitric: +Here's a minimal example to get you started: ```javascript !! import { api } from '@nitric/sdk' -// each API needs a unique name -const galaxyApi = api('far-away-galaxy-api') +const myApi = api('my-api') -galaxyApi.get('/moon', async ({ req, res }) => { - res.body = "that's no moon, it's a space station." +myApi.get('/hello', async (ctx) => { + ctx.res.body = 'Hello World!' }) ``` ```typescript !! import { api } from '@nitric/sdk' -// each API needs a unique name -const galaxyApi = api('far-away-galaxy-api') +const myApi = api('my-api') -galaxyApi.get('/moon', async ({ req, res }) => { - res.body = "that's no moon, it's a space station." +myApi.get('/hello', async (ctx) => { + ctx.res.body = 'Hello World!' }) ``` @@ -42,12 +36,11 @@ galaxyApi.get('/moon', async ({ req, res }) => { from nitric.resources import api from nitric.application import Nitric -# each API needs a unique name -galaxy_api = api('far-away-galaxy-api') +my_api = api('my-api') -@galaxy_api.get("/moon") -async def get_moon(ctx): - ctx.res.body = "that's no moon, it's a space station." +@my_api.get("/hello") +async def hello(ctx): + ctx.res.body = "Hello World!" Nitric.run() ``` @@ -61,9 +54,10 @@ import ( ) func main() { - galaxyApi := nitric.NewApi("far-away-galaxy-api") - galaxyApi.Get("/moon", func(ctx *apis.Ctx) { - ctx.Response.Body = []byte("that's no moon, it's a space station.") + myApi := nitric.NewApi("my-api") + + myApi.Get("/hello", func(ctx *apis.Ctx) { + ctx.Response.Body = []byte("Hello World!") }) nitric.Run() @@ -73,1248 +67,424 @@ func main() { ```dart !! import 'package:nitric_sdk/nitric.dart'; -// each API needs a unique name -final galaxyApi = Nitric.api("far-away-galaxy-api"); - -galaxyApi.get("/moon", (ctx) async { - ctx.res.body = "that's no moon, it's a space station."; +final myApi = Nitric.api("my-api"); +myApi.get("/hello", (ctx) async { + ctx.res.body = "Hello World!"; return ctx; }); ``` -## Routing +## Core Concepts -You can define routes and handler services for incoming requests using methods on your API objects. +### API Resources -For example, you can declare a route that handles `POST` requests using the `post()` method. When declaring routes you provide the path to match and a callback that will serve as the handler for matching requests. +Each API in your application needs a unique name. This name is used to identify the API across your project and in cloud deployments. - - Depending on the language SDK, callbacks are either passed as parameters or - defined using decorators. - +```javascript +const myApi = api('my-api-name') +``` + +### Request Context + +Every handler receives a context object (`ctx`) that contains: + +- `req`: The incoming request object +- `res`: The response object you'll use to send data back + +### Routing + +APIs support all standard HTTP methods and path-based routing: ```javascript !! -import { getPlanetList, createPlanet } from 'planets' +const myApi = api('my-api') -galaxyApi.get('/planets', async (ctx) => { - ctx.res.json(getPlanetList()) +// GET /items +myApi.get('/items', async (ctx) => { + ctx.res.json({ items: [] }) }) -galaxyApi.post('/planets', async (ctx) => { - createPlanet(ctx.req.json()) +// POST /items +myApi.post('/items', async (ctx) => { + const item = ctx.req.json() ctx.res.status = 201 + ctx.res.json(item) +}) + +// PUT /items/:id +myApi.put('/items/:id', async (ctx) => { + const { id } = ctx.req.params + const item = ctx.req.json() + ctx.res.json({ id, ...item }) }) ``` ```typescript !! -import { getPlanetList, createPlanet } from 'planets' +const myApi = api('my-api') -galaxyApi.get('/planets', async (ctx) => { - ctx.res.json(getPlanetList()) +// GET /items +myApi.get('/items', async (ctx) => { + ctx.res.json({ items: [] }) }) -galaxyApi.post('/planets', async (ctx) => { - createPlanet(ctx.req.json()) +// POST /items +myApi.post('/items', async (ctx) => { + const item = ctx.req.json() ctx.res.status = 201 + ctx.res.json(item) +}) + +// PUT /items/:id +myApi.put('/items/:id', async (ctx) => { + const { id } = ctx.req.params + const item = ctx.req.json() + ctx.res.json({ id, ...item }) }) ``` ```python !! -from planets import get_planets_list, create_planet +my_api = api('my-api') -@galaxy_api.get("/planets") -async def list_planets(ctx): - ctx.res.body = get_planets_list() +@my_api.get("/items") +async def list_items(ctx): + ctx.res.json({"items": []}) -@galaxy_api.post("/planets") -async def create_planet(ctx): - create_planet(ctx.req.json) - ctx.res.status = 201 +@my_api.post("/items") +async def create_item(ctx): + item = ctx.req.json + ctx.res.status = 201 + ctx.res.json(item) + +@my_api.put("/items/:id") +async def update_item(ctx): + id = ctx.req.params["id"] + item = ctx.req.json + ctx.res.json({"id": id, **item}) ``` ```go !! -package main - -import ( - "github.com/nitrictech/go-sdk/nitric" - "github.com/nitrictech/go-sdk/nitric/apis" -) - -func main() { - galaxyApi := nitric.NewApi("far-away-galaxy-api") +myApi := nitric.NewApi("my-api") - galaxyApi.Get("/planets", func(ctx *apis.Ctx) { - ctx.Response.Headers = map[string][]string{"Content-Type": {"application/json"}} - ctx.Response.Body = []byte(GetPlanetList()) - }) +myApi.Get("/items", func(ctx *apis.Ctx) { + ctx.Response.Json(map[string]interface{}{"items": []}) +}) - galaxyApi.Post("/planets", func(ctx *apis.Ctx) { - CreatePlanet(ctx.Request.Data()) +myApi.Post("/items", func(ctx *apis.Ctx) { + item := ctx.Request.Json() ctx.Response.Status = 201 - }) - - nitric.Run() -} + ctx.Response.Json(item) +}) +myApi.Put("/items/:id", func(ctx *apis.Ctx) { + id := ctx.Request.PathParams()["id"] + item := ctx.Request.Json() + ctx.Response.Json(map[string]interface{}{"id": id, "item": item}) +}) ``` ```dart !! -import 'package:planets' - -galaxyApi.get("/planets", (ctx) async { - ctx.res.json(getPlanetList()); +final myApi = Nitric.api("my-api"); +myApi.get("/items", (ctx) async { + ctx.res.json({"items": []}); return ctx; }); -galaxyApi.post("/planets", (ctx) async { - createPlanet(ctx.req.json()); +myApi.post("/items", (ctx) async { + final item = ctx.req.json; ctx.res.status = 201; - + ctx.res.json(item); return ctx; }); -``` - - - -### Request Context - -Nitric provides callbacks with a single context object that gives you everything you need to read requests and write responses. By convention this object is typically named `ctx`. - -The context object includes a request `req` and response `res`, which in turn provide convenient methods for reading and writing bodies, as well as auto-extracted parameters and HTTP headers. - -#### Parameters - -The path string used to declare routes can include named parameters. The values collected from those parameters are automatically included in the context object under `ctx.req.params`. - -Path parameters are denoted by a colon prefix `:` - - - -```javascript !! -import { getPlanet } from 'planets' - -// create a dynamic route and extract the parameter `name` -galaxyApi.get('/planets/:name', async (ctx) => { - const { name } = ctx.req.params - ctx.res.json(getPlanet(name)) -}) -``` - -```typescript !! -import { getPlanet } from 'planets' - -// create a dynamic route and extract the parameter `name` -galaxyApi.get('/planets/:name', async (ctx) => { - const { name } = ctx.req.params - ctx.res.json(getPlanet(name)) -}) -``` - -```python !! -from planets import get_planet - -# create a dynamic route and extract the parameter `name` -@galaxy_api.get("/planets/:name") -async def get_planet_route(ctx): - name = ctx.req.params['name'] - ctx.res.body = get_planet(name) -``` - -```go !! -package main - -import ( - "github.com/nitrictech/go-sdk/nitric" - "github.com/nitrictech/go-sdk/nitric/apis" -) - -func main() { - galaxyApi := nitric.NewApi("far-away-galaxy-api") - - // create a dynamic route and extract the parameter `name` - galaxyApi.Get("/planets/:name", func(ctx *apis.Ctx) { - name := ctx.Request.PathParams()["name"] - - ctx.Response.Body = []byte(GetPlanet(name)) - }) - - nitric.Run() -} - -``` - -```dart !! -import 'package:planets' - -// create a dynamic route and extract the parameter `name` -galaxyApi.get("/planets/:name", (ctx) async { - final name = ctx.req.pathParams["name"]!; - - ctx.res.json(getPlanet(name)); +myApi.put("/items/:id", (ctx) async { + final id = ctx.req.pathParams["id"]; + final item = ctx.req.json; + ctx.res.json({"id": id, ...item}); return ctx; }); ``` -#### HTTP status and headers +### Path Parameters -The response object provides `status` and `headers` properties you can use to return HTTP status codes and headers. +You can define dynamic routes using path parameters: ```javascript !! -// return a redirect response using status 301 -galaxyApi.get('/planets/alderaan', async (ctx) => { - ctx.res.status = 301 - ctx.res.headers['Location'] = ['https://example.org/debris/alderaan'] +myApi.get('/users/:id', async (ctx) => { + const { id } = ctx.req.params + ctx.res.json({ id }) }) ``` ```typescript !! -// return a redirect response using status 301 -galaxyApi.get('/planets/alderaan', async (ctx) => { - ctx.res.status = 301 - ctx.res.headers['Location'] = ['https://example.org/debris/alderaan'] +myApi.get('/users/:id', async (ctx) => { + const { id } = ctx.req.params + ctx.res.json({ id }) }) ``` ```python !! -# return a redirect response using status 301 -@galaxy_api.get("/planets/alderaan") -async def find_alderaan(ctx): - ctx.res.status = 301 - ctx.res.headers["Location"] = "https://example.org/debris/alderaan" +@my_api.get("/users/:id") +async def get_user(ctx): + id = ctx.req.params["id"] + ctx.res.json({"id": id}) ``` ```go !! -// return a redirect response using status 301 -galaxyApi.Get("/planets/alderaan", func(ctx *apis.Ctx) { - ctx.Response.Status = 301 - ctx.Response.Location = "https://example.org/debris/alderaan" +myApi.Get("/users/:id", func(ctx *apis.Ctx) { + id := ctx.Request.PathParams()["id"] + ctx.Response.Json(map[string]interface{}{"id": id}) }) ``` ```dart !! -// return a redirect response using status 301 -galaxyApi.get("/planets/alderaan", (ctx) async { - ctx.res.status = 301; - ctx.res.headers["Location"] = ["https://example.org/debris/alderaan"]; - +myApi.get("/users/:id", (ctx) async { + final id = ctx.req.pathParams["id"]; + ctx.res.json({"id": id}); return ctx; }); ``` -## API Security - -APIs can include security definitions for OIDC-compatible providers such as [Auth0](https://auth0.com/), [FusionAuth](https://fusionauth.io/) and [AWS Cognito](https://aws.amazon.com/cognito/). - - - Applying security at the API allows AWS, Google Cloud and Azure to reject - unauthenticated or unauthorized requests at the API Gateway, before invoking - your application code. In serverless environments this reduces costs by - limiting application load from unwanted or malicious requests. - - - - Security rules are currently **not enforced** when using nitric for **local** - development. - - -### Authentication - -APIs can be configured to automatically authenticate and allow or reject incoming requests. A `securityDefinitions` object can be provided, which _defines_ the authentication requirements that can later be enforced by the API. - -The security definition describes the kind of authentication to perform and the configuration required to perform it. For a [JWT](https://jwt.io) this configuration includes the JWT issuer and audiences. +## Advanced Features - - Security definitions only define **available** security requirements for an - API, they don't automatically **apply** those requirements. - +### Middleware -Once a security definition is available it can be applied to the entire API or selectively to individual routes. +Middleware allows you to add common functionality across routes. Each middleware receives the context and a `next` function to continue the request chain: ```javascript !! -import { api, oidcRule } from '@nitric/sdk' - -const defaultSecurityRule = oidcRule({ - name: 'default', - audiences: ['https://test-security-definition/'], - issuer: 'https://dev-abc123.us.auth0.com', -}) +async function authMiddleware(ctx, next) { + const token = ctx.req.headers['authorization'] + if (!token) { + ctx.res.status = 401 + return ctx + } + return await next(ctx) +} -const secureApi = api('main', { - // apply the security definition to all routes in this API. - security: [defaultSecurityRule()], +const secureApi = api('secure-api', { + middleware: [authMiddleware], }) ``` ```typescript !! -import { api, oidcRule } from '@nitric/sdk' - -const defaultSecurityRule = oidcRule({ - name: 'default', - audiences: ['https://test-security-definition/'], - issuer: 'https://dev-abc123.us.auth0.com', -}) +async function authMiddleware(ctx, next) { + const token = ctx.req.headers['authorization'] + if (!token) { + ctx.res.status = 401 + return ctx + } + return await next(ctx) +} -const secureApi = api('main', { - // apply the security definition to all routes in this API. - security: [defaultSecurityRule()], +const secureApi = api('secure-api', { + middleware: [authMiddleware], }) ``` ```python !! -from nitric.resources import api, ApiOptions, oidc_rule -from nitric.application import Nitric - -default_security_rule = oidc_rule( - name="default", - audiences=["https://test-security-definition/"], - issuer="https://dev-abc123.us.auth0.com", -) +async def auth_middleware(ctx, next): + token = ctx.req.headers.get("authorization") + if not token: + ctx.res.status = 401 + return ctx + return await next(ctx) -secure_api = api("main", opts=ApiOptions( - # apply the security definition to all routes in this API. - security=[default_security_rule()], - ) -) - -Nitric.run() +secure_api = api("secure-api", opts=ApiOptions(middleware=[auth_middleware])) ``` ```go !! -package main - -import ( - "github.com/nitrictech/go-sdk/nitric" - "github.com/nitrictech/go-sdk/nitric/apis" -) - -func main() { - defaultSecurityRule := apis.OidcRule( - "default", - "https://dev-abc123.us.auth0.com/.well-known/openid-configuration", - []string{"https://test-security-definition"}, - ) - - secureApi := nitric.NewApi( - "main", - apis.WithSecurity(defaultSecurityRule([]string{})), - ) - - nitric.Run() +func authMiddleware(next apis.Handler) apis.Handler { + return func(ctx *apis.Ctx) error { + if ctx.Request.Headers()["authorization"] == nil { + ctx.Response.Status = 401 + return nil + } + return next(ctx) + } } + +secureApi := nitric.NewApi("secure-api", apis.WithMiddleware(authMiddleware)) ``` ```dart !! -import 'package:nitric_sdk/nitric.dart'; - -// define your security definition -final defaultSecurityRule = Nitric.oidcRule( - "default", - "https://dev-abc123.us.auth0.com", - ["https://test-security-definition/"] -); +Future authMiddleware(HttpContext ctx) async { + if (!ctx.req.headers.containsKey("authorization")) { + ctx.res.status = 401; + return ctx; + } + return ctx.next(); +} final secureApi = Nitric.api( - "main", - opts: ApiOptions( - security: [ - // apply the security definition to all routes in this API. - defaultSecurityRule([]) - ] - ) + "secure-api", + opts: ApiOptions(middlewares: [authMiddleware]) ); ``` -### Authorization - -In addition to authentication, Nitric APIs can also be configured to perform authorization based on scopes. Again, this can be done at the top level of the API or on individual routes. +### Security -Add the required scopes to the `security` object when applying a security definition. +APIs can be secured using OIDC-compatible providers like Auth0, FusionAuth, or AWS Cognito. Security can be applied at the API level or per route: ```javascript !! import { api, oidcRule } from '@nitric/sdk' -const defaultSecurityRule = oidcRule({ - name: 'default', - audiences: ['https://test-security-definition/'], - issuer: 'https://dev-abc123.us.auth0.com', +const auth0Rule = oidcRule({ + name: 'auth0', + issuer: 'https://your-tenant.auth0.com', + audiences: ['your-api-identifier'], }) -const secureApi = api('main', { - // apply the security definition to all routes in this API. - // add scopes to the rule to authorize - security: [defaultSecurityRule('user.read')], +const secureApi = api('secure-api', { + security: [auth0Rule('user.read')], }) ``` ```typescript !! import { api, oidcRule } from '@nitric/sdk' -const defaultSecurityRule = oidcRule({ - name: 'default', - audiences: ['https://test-security-definition/'], - issuer: 'https://dev-abc123.us.auth0.com', +const auth0Rule = oidcRule({ + name: 'auth0', + issuer: 'https://your-tenant.auth0.com', + audiences: ['your-api-identifier'], }) -const secureApi = api('main', { - // apply the security definition to all routes in this API. - // add scopes to the rule to authorize - security: [defaultSecurityRule('user.read')], +const secureApi = api('secure-api', { + security: [auth0Rule('user.read')], }) ``` ```python !! from nitric.resources import api, ApiOptions, oidc_rule -from nitric.application import Nitric - -default_security_rule = oidc_rule( - name="default", - audiences=["https://test-security-definition/"], - issuer="https://dev-abc123.us.auth0.com", -) -secure_api = api("main", opts=ApiOptions( - # apply the security definition to all routes in this API. - security=[default_security_rule("user.read")], - ) +auth0_rule = oidc_rule( + name="auth0", + issuer="https://your-tenant.auth0.com", + audiences=["your-api-identifier"] ) -Nitric.run() +secure_api = api("secure-api", opts=ApiOptions( + security=[auth0_rule("user.read")] +)) ``` ```go !! -package main - -import ( - "github.com/nitrictech/go-sdk/nitric" - "github.com/nitrictech/go-sdk/nitric/apis" +auth0Rule := apis.OidcRule( + "auth0", + "https://your-tenant.auth0.com/.well-known/openid-configuration", + []string{"your-api-identifier"}, ) -func main() { - defaultSecurityRule := apis.OidcRule( - "default", - "https://dev-abc123.us.auth0.com/.well-known/openid-configuration", - []string{"https://test-security-definition/"}, - ) - - secureApi := nitric.NewApi( - "main", - apis.WithSecurity(defaultSecurityRule([]string{"user.read"})), - ) - - nitric.Run() -} - +secureApi := nitric.NewApi( + "secure-api", + apis.WithSecurity(auth0Rule([]string{"user.read"})), +) ``` ```dart !! -import 'package:nitric_sdk/nitric.dart'; - -// define your security definition -final defaultSecurityRule = Nitric.oidcRule( - "default", - "https://dev-abc123.us.auth0.com", - ["https://test-security-definition/"] +final auth0Rule = Nitric.oidcRule( + "auth0", + "https://your-tenant.auth0.com", + ["your-api-identifier"] ); final secureApi = Nitric.api( - "main", + "secure-api", opts: ApiOptions( - security: [ - // apply the security definition to all routes in this API. - defaultSecurityRule(["user.read"]) - ] + security: [auth0Rule(["user.read"])] ) ); ``` -For an in-depth tutorial look at the [Auth0 integration guide](/guides/nodejs/secure-api-auth0) - -### Override API-level security - -Individual routes can have their own security rules which apply any available `securityDefinition`. The requirement defined on routes override any requirements previously set at the top level of the API. - -This allows you to selectively increase or decrease the security requirements for specific routes. - - - -```javascript !! -galaxyApi.get('planets/unsecured-planet', async (ctx) => {}, { - // override top level security to remove security from this route - security: [], -}) - -galaxyApi.post('planets/secured-planet', async (ctx) => {}, { - // override top level security to require user.write scope to access - security: [customSecurityRule('user.write')], -}) -``` - -```typescript !! -galaxyApi.get('planets/unsecured-planet', async (ctx) => {}, { - // override top level security to remove security from this route - security: [], -}) - -galaxyApi.post('planets/secured-planet', async (ctx) => {}, { - // override top level security to require user.write scope to access - security: [customSecurityRule('user.write')], -}) -``` - -```python !! -# override top level security to remove security from this route -@galaxy_api.get("planets/unsecured-planet", opts=MethodOptions(security=[])) -async def get_planet(ctx): - pass - -# override top level security to require user.write scope to access -@galaxy_api.post("planets/secured-planet", opts=MethodOptions(security=[custom_rule("user.write")])) -async def get_planet(ctx): - pass -``` +### Custom Domains -```go !! -// override top level security to remove security from this route -secureApi.Get("/planets/unsecured-planet", func(ctx *apis.Ctx) { - // Handle request -}, apis.WithNoMethodSecurity()) - -// override top level security to require user.write scope to access -secureApi.Get("/planets/unsecured-planet", func(ctx *apis.Ctx) { - // Handle request -}, apis.WithSecurity(customRule([]string{"users:write"}))) -``` +You can configure custom domains for your APIs in your stack configuration: -```dart !! -// override top level security to remove security from this route -galaxyApi.get("/planets/unsecured-planet", (ctx) async { - return ctx; -}, security: []); +```yaml title:nitric.prod.yaml +provider: nitric/aws@1.1.0 +region: us-east-1 -// override top level security to require user.write scope to access -galaxyApi.post("/planets/unsecured-planet", (ctx) async { - return ctx; -}, security: [customRule(["user.write"])]); +apis: + my-api: + domains: + - api.example.com ``` - - -## Defining Middleware - -Behavior that's common to several APIs or routes can be applied using middleware. Multiple middleware can also be composed to create a cascading set of steps to perform on incoming requests or outgoing responses. - -In most of Nitric's supported languages middleware functions look nearly identical to handlers except for an additional parameter called `next`, which is the next middleware or handler to be called in the chain. By providing each middleware the next middleware in the chain it allows them to intercept requests, response and errors to perform operations like logging, decoration, exception handling and many other common tasks. + + Custom domains are currently only supported for AWS deployments. See the [AWS + Custom Domain Prerequisites](#aws-custom-domain-prerequisites) section for + setup details. + - +## Project Organization -```javascript !! -async function validate(ctx, next) { - if (!ctx.req.headers['content-type']) { - ctx.res.status = 400 - ctx.res.body = 'header Content-Type is required' - return ctx - } - return await next(ctx) -} -``` +### Multiple Services -```typescript !! -async function validate(ctx, next) { - if (!ctx.req.headers['content-type']) { - ctx.res.status = 400 - ctx.res.body = 'header Content-Type is required' - return ctx - } - return await next(ctx) -} -``` +You can split your API routes across multiple services while using the same API resource: -```python !! -async def validate(ctx, nxt: HttpMiddleware): - if ctx.req.headers['content-type'] is None: - ctx.res.status = 400 - ctx.res.body = "header Content-Type is required" - return ctx - return await nxt(ctx) -``` +```javascript title:services/users.js +import { api } from '@nitric/sdk' -```go !! -// Using the Go SDK we recommend using higher-order functions to define middleware -func validate(next apis.Handler) apis.Handler { - return func (ctx *apis.Ctx) error { - if ctx.Request.Headers()["content-type"] == nil { - ctx.Response.Status = 400 - ctx.Response.Body = []byte("header Content-Type is required") - - return nil - } +const myApi = api('my-api') - return next(ctx) - } -} +myApi.get('/users', async (ctx) => { + // Handle user listing +}) ``` -```dart !! -Future validate(HttpContext ctx) async { - if (!ctx.req.headers.containsKey("Content-Type")) { - ctx.res.status = 400; - ctx.res.body = "header Content-Type is required"; +```javascript title:services/products.js +import { api } from '@nitric/sdk' - return ctx; - } +const myApi = api('my-api') - return ctx.next(); -} +myApi.get('/products', async (ctx) => { + // Handle product listing +}) ``` - - -### API level middleware - -Middleware defined at the API level will be called on every request to every route. - - - -```javascript !! -import { api } from '@nitric/sdk' -import { validate, logRequest } from '../middleware' +### Shared Resources -const customersApi = api('customers', { - middleware: [logRequest, validate], -}) -``` +For better organization, you can define shared API resources: -```typescript !! +```javascript title:resources/api.js import { api } from '@nitric/sdk' -import { validate, logRequest } from '../middleware' -const customersApi = api('customers', { - middleware: [logRequest, validate], -}) +export const myApi = api('my-api') ``` -```python !! -from nitric.resources import api, ApiOptions -from common.middleware import validate, log_request -from nitric.application import Nitric +Then import and use them in your services: -customers_api = api("customers", opts=ApiOptions(middleware=[log_request, validate])) +```javascript title:services/users.js +import { myApi } from '../resources/api' -Nitric.run() +myApi.get('/users', async (ctx) => { + // Handle user listing +}) ``` -```go !! -import ( - "github.com/nitrictech/go-sdk/nitric" - "github.com/nitrictech/go-sdk/nitric/apis" -) - -func validate(next apis.Handler) apis.Handler { - return func(ctx *apis.Ctx) error { - if ctx.Request.Headers()["content-type"] == nil { - ctx.Response.Status = 400 - ctx.Response.Body = []byte("header Content-Type is required") +## Cloud Provider Support - return nil - } +Each cloud provider has specific features and limitations for API deployments: - return next(ctx) - } -} +- [AWS](/providers/mappings/aws/apis) +- [Azure](/providers/mappings/azure/apis) +- [Google Cloud](/providers/mappings/gcp/apis) -func main() { - customersApi := nitric.NewApi( - "customers", - apis.WithMiddleware(validate)) - - nitric.Run() -} -``` - -```dart !! -import 'package:nitric_sdk/nitric.dart'; -import '../middlewares'; - -final customersApi = Nitric.api( - "customers", - opts: ApiOptions( - middlewares: [logRequest, validate], - ) -); -``` - - - -### Route level middleware - -Middleware defined at the route level will only be called for that route. - - - -```javascript !! -import { api } from '@nitric/sdk' -import { validate } from '../middleware' - -const customersApi = api('customers') - -const getAllCustomers = (ctx) => {} - -// Inline using .get() -customersApi.get('/customers', [validate, getAllCustomers]) - -// Using .route() -customersApi.route('/customers').get([validate, getAllCustomers]) -``` - -```typescript !! -import { api } from '@nitric/sdk' -import { validate } from '../middleware' - -const customersApi = api('customers') - -const getAllCustomers = (ctx) => {} - -// Inline using .get() -customersApi.get('/customers', [validate, getAllCustomers]) - -// Using .route() -customersApi.route('/customers').get([validate, getAllCustomers]) -``` - -```python !! -# Route level middleware currently not supported in python -``` - -```go !! -import ( - "github.com/nitrictech/go-sdk/nitric" - "github.com/nitrictech/go-sdk/nitric/apis" -) - -func validate(next apis.Handler) apis.Handler { - return func(ctx *apis.Ctx) error { - if ctx.Request.Headers()["content-type"] == nil { - ctx.Response.Status = 400 - ctx.Response.Body = []byte("header Content-Type is required") - - return nil - } - - return next(ctx) - } -} - -func main() { - customersApi := nitric.NewApi("customers") - - customersApi.Get("/customers", validate(func(ctx *apis.Ctx) error { - // handle request - return nil - })) - - nitric.Run() -} -``` - -```dart !! -import 'package:nitric_sdk/nitric.dart'; -import '../middlewares'; - -Future getAllCustomers(HttpContext ctx) async { - // gets the customers - return ctx.next(); -} - -final customersApi = Nitric.api("customers"); - -// Inline using .get() -customersApi.get("/customers", getAllCustomers, middlewares: [logRequest, validate]); - -// Inline using .route() -customersApi.route("/customers", middlewares: [logRequest, validate]).get(getAllCustomers); -``` - - - -## Custom Domains - -Custom domains are currently only supported for AWS deployments. - -By default APIs deployed by Nitric will be assigned a domain by the target cloud provider. If you would like to deploy APIs with predefined custom domains you can specify the custom domains for each API in your project's stack files. For these domains to be successfully configured you will need to meet the prerequisites defined for each cloud provider below. - - - - - -```yaml title:nitric.prod.yaml -provider: nitric/aws@1.1.0 -region: ap-southeast-2 - -# Add a key for configuring apis -apis: - # Target an API by its nitric name - my-api-name: - # provide domains to be used for the api - domains: - - test.example.com -``` - - - - - -```yaml title:nitric.prod.yaml -# currently unsupported - request support here: https://github.com/nitrictech/nitric/issues -``` - - - - - -```yaml title:nitric.prod.yaml -# currently unsupported - request support here: https://github.com/nitrictech/nitric/issues -``` - - - - - -## Custom Descriptions - -By default, APIs will not be deployed with a description. You can add a description using the following configuration in your stack file. - - - - - -```yaml title:nitric.prod.yaml -provider: nitric/aws@1.12.4 -region: ap-southeast-2 - -# Add a key for configuring apis -apis: - # Target an API by its nitric name - my-api-name: - # provide domains to be used for the api - description: An AWS API -``` - - - - - -```yaml title:nitric.prod.yaml -provider: nitric/azure@1.12.4 -region: Australia East -org: example-org -adminemail: test@example.com - -apis: - # Target an API by its nitric name - my-api-name: - # provide domains to be used for the api - description: An Azure API -``` - - - - - -```yaml title:nitric.prod.yaml -provider: nitric/gcp@1.12.4 -region: australia-southeast1 - -# Add a key for configuring apis -apis: - # Target an API by its nitric name - my-api-name: - # provide domains to be used for the api - description: A GCP API -``` - - - - - -### AWS Custom Domain Prerequisites - -To support custom domains with APIs deployed to AWS your domain (or subdomain) will need to be setup as a [hosted zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-working-with.html) in Route 53. - -The general steps to setup a hosted zone in Route 53 are as follows: - -- Navigate to Route 53 in the AWS Console -- Select 'hosted zones' from the left navigation -- Click 'Create hosted zone' -- Enter your domain name and choose the 'Public hosted zone' type. -- Click 'Create hosted zone' -- You will now be provided with a set of NS DNS records to configure in the DNS provider for your domain -- Create the required DNS records, then wait for the DNS changes to propagate - -Once this is done you will be able to use the hosted zone domain or any direct subdomain with your Nitric APIs. - -You can read more about how AWS suggests configuring hosted zones in their documentation on [Making Route 53 the DNS service for a domain that's in use](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/migrate-dns-domain-in-use.html) or [Making Route 53 the DNS service for an inactive domain](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/migrate-dns-domain-inactive.html) - - - If the hosted zone was `nitric.io`, `nitric.io` or `api.nitric.io` would be - supported for APIs, but not `public.api.nitric.io` since that is a subdomain - of a subdomain. - - - - DNS propagation of the NS records can take a few seconds to a few hours due to - the nature of DNS. - - -If you're more of a visual learner, below is a video that walks through how to set up your custom domains. - -
-
-