diff --git a/docs/apis.mdx b/docs/apis.mdx
index afc4d0af5..b32cca217 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,811 +67,388 @@ 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.
-
-
- Depending on the language SDK, callbacks are either passed as parameters or
- defined using decorators.
-
+Each API in your application needs a unique name. This name is used to identify the API across your project and in cloud deployments.
```javascript !!
-import { getPlanetList, createPlanet } from 'planets'
-
-galaxyApi.get('/planets', async (ctx) => {
- ctx.res.json(getPlanetList())
-})
-
-galaxyApi.post('/planets', async (ctx) => {
- createPlanet(ctx.req.json())
- ctx.res.status = 201
-})
+const myApi = api('my-api-name')
```
```typescript !!
-import { getPlanetList, createPlanet } from 'planets'
-
-galaxyApi.get('/planets', async (ctx) => {
- ctx.res.json(getPlanetList())
-})
-
-galaxyApi.post('/planets', async (ctx) => {
- createPlanet(ctx.req.json())
- ctx.res.status = 201
-})
+const myApi = api('my-api-name')
```
```python !!
-from planets import get_planets_list, create_planet
-
-@galaxy_api.get("/planets")
-async def list_planets(ctx):
- ctx.res.body = get_planets_list()
-
-@galaxy_api.post("/planets")
-async def create_planet(ctx):
- create_planet(ctx.req.json)
- ctx.res.status = 201
+my_api = api('my-api-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")
-
- galaxyApi.Get("/planets", func(ctx *apis.Ctx) {
- ctx.Response.Headers = map[string][]string{"Content-Type": {"application/json"}}
- ctx.Response.Body = []byte(GetPlanetList())
- })
-
- galaxyApi.Post("/planets", func(ctx *apis.Ctx) {
- CreatePlanet(ctx.Request.Data())
- ctx.Response.Status = 201
- })
-
- nitric.Run()
-}
-
+myApi := nitric.NewApi("my-api-name")
```
```dart !!
-import 'package:planets'
-
-galaxyApi.get("/planets", (ctx) async {
- ctx.res.json(getPlanetList());
-
- return ctx;
-});
-
-galaxyApi.post("/planets", (ctx) async {
- createPlanet(ctx.req.json());
- ctx.res.status = 201;
-
- return ctx;
-});
+final myApi = Nitric.api("my-api-name");
```
### 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`.
+Every handler receives a context object (`ctx`) that contains:
-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.
+- `req`: The incoming request object
+- `res`: The response object you'll use to send data back
-#### Parameters
+### Routing
-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 `:`
+APIs support all standard HTTP methods and path-based routing:
```javascript !!
-import { getPlanet } from 'planets'
+const myApi = api('my-api')
-// 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))
+// GET /items
+myApi.get('/items', async (ctx) => {
+ ctx.res.json({ items: [] })
})
-```
-```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))
+// POST /items
+myApi.post('/items', async (ctx) => {
+ const item = ctx.req.json()
+ ctx.res.status = 201
+ ctx.res.json(item)
})
-```
-
-```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));
-
- return ctx;
-});
-```
-
-
-
-#### HTTP status and headers
-
-The response object provides `status` and `headers` properties you can use to return HTTP status codes and headers.
-
-
-
-```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']
+// 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 !!
-// 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']
-})
-```
-
-```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"
-```
+const myApi = api('my-api')
-```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"
+// GET /items
+myApi.get('/items', async (ctx) => {
+ ctx.res.json({ items: [] })
})
-```
-```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"];
-
- 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.
-
-
- Security definitions only define **available** security requirements for an
- API, they don't automatically **apply** those requirements.
-
-
-Once a security definition is available it can be applied to the entire API or selectively to individual routes.
-
-
-
-```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 secureApi = api('main', {
- // apply the security definition to all routes in this API.
- security: [defaultSecurityRule()],
-})
-```
-
-```typescript !!
-import { api, oidcRule } from '@nitric/sdk'
-
-const defaultSecurityRule = oidcRule({
- name: 'default',
- audiences: ['https://test-security-definition/'],
- issuer: 'https://dev-abc123.us.auth0.com',
+// POST /items
+myApi.post('/items', async (ctx) => {
+ const item = ctx.req.json()
+ ctx.res.status = 201
+ ctx.res.json(item)
})
-const secureApi = api('main', {
- // apply the security definition to all routes in this API.
- security: [defaultSecurityRule()],
+// 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 nitric.resources import api, ApiOptions, oidc_rule
-from nitric.application import Nitric
+my_api = api('my-api')
-default_security_rule = oidc_rule(
- name="default",
- audiences=["https://test-security-definition/"],
- issuer="https://dev-abc123.us.auth0.com",
-)
+@my_api.get("/items")
+async def list_items(ctx):
+ ctx.res.json({"items": []})
-secure_api = api("main", opts=ApiOptions(
- # apply the security definition to all routes in this API.
- security=[default_security_rule()],
- )
-)
+@my_api.post("/items")
+async def create_item(ctx):
+ item = ctx.req.json
+ ctx.res.status = 201
+ ctx.res.json(item)
-Nitric.run()
+@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() {
- 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{})),
- )
+myApi := nitric.NewApi("my-api")
- nitric.Run()
-}
-```
-
-```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 secureApi = Nitric.api(
- "main",
- opts: ApiOptions(
- security: [
- // apply the security definition to all routes in this API.
- defaultSecurityRule([])
- ]
- )
-);
-```
-
-
-
-### 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.
-
-Add the required scopes to the `security` object when applying a security definition.
-
-
-
-```javascript !!
-import { api, oidcRule } from '@nitric/sdk'
-
-const defaultSecurityRule = oidcRule({
- name: 'default',
- audiences: ['https://test-security-definition/'],
- issuer: 'https://dev-abc123.us.auth0.com',
+myApi.Get("/items", func(ctx *apis.Ctx) {
+ ctx.Response.Json(map[string]interface{}{"items": []})
})
-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')],
-})
-```
-
-```typescript !!
-import { api, oidcRule } from '@nitric/sdk'
-
-const defaultSecurityRule = oidcRule({
- name: 'default',
- audiences: ['https://test-security-definition/'],
- issuer: 'https://dev-abc123.us.auth0.com',
+myApi.Post("/items", func(ctx *apis.Ctx) {
+ item := ctx.Request.Json()
+ ctx.Response.Status = 201
+ ctx.Response.Json(item)
})
-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')],
+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})
})
```
-```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")],
- )
-)
-
-Nitric.run()
-```
-
-```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{"user.read"})),
- )
-
- nitric.Run()
-}
-
-```
-
```dart !!
-import 'package:nitric_sdk/nitric.dart';
+final myApi = Nitric.api("my-api");
-// define your security definition
-final defaultSecurityRule = Nitric.oidcRule(
- "default",
- "https://dev-abc123.us.auth0.com",
- ["https://test-security-definition/"]
-);
+myApi.get("/items", (ctx) async {
+ ctx.res.json({"items": []});
+ return ctx;
+});
-final secureApi = Nitric.api(
- "main",
- opts: ApiOptions(
- security: [
- // apply the security definition to all routes in this API.
- defaultSecurityRule(["user.read"])
- ]
- )
-);
+myApi.post("/items", (ctx) async {
+ final item = ctx.req.json;
+ ctx.res.status = 201;
+ ctx.res.json(item);
+ return ctx;
+});
+
+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;
+});
```
-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.
+### Path Parameters
-This allows you to selectively increase or decrease the security requirements for specific routes.
+You can define dynamic routes using path parameters:
```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')],
+myApi.get('/users/:id', async (ctx) => {
+ const { id } = ctx.req.params
+ ctx.res.json({ id })
})
```
```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')],
+myApi.get('/users/:id', async (ctx) => {
+ const { id } = ctx.req.params
+ ctx.res.json({ id })
})
```
```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
+@my_api.get("/users/:id")
+async def get_user(ctx):
+ id = ctx.req.params["id"]
+ ctx.res.json({"id": id})
```
```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"})))
+myApi.Get("/users/:id", func(ctx *apis.Ctx) {
+ id := ctx.Request.PathParams()["id"]
+ ctx.Response.Json(map[string]interface{}{"id": id})
+})
```
```dart !!
-// override top level security to remove security from this route
-galaxyApi.get("/planets/unsecured-planet", (ctx) async {
- return ctx;
-}, security: []);
-
-// override top level security to require user.write scope to access
-galaxyApi.post("/planets/unsecured-planet", (ctx) async {
+myApi.get("/users/:id", (ctx) async {
+ final id = ctx.req.pathParams["id"];
+ ctx.res.json({"id": id});
return ctx;
-}, security: [customRule(["user.write"])]);
+});
```
-## Defining Middleware
+## Advanced Features
-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.
+### Middleware
-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.
+Middleware allows you to add common functionality across routes. Each middleware receives the context and a `next` function to continue the request chain:
```javascript !!
-async function validate(ctx, next) {
- if (!ctx.req.headers['content-type']) {
- ctx.res.status = 400
- ctx.res.body = 'header Content-Type is required'
+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('secure-api', {
+ middleware: [authMiddleware],
+})
```
```typescript !!
-async function validate(ctx, next) {
- if (!ctx.req.headers['content-type']) {
- ctx.res.status = 400
- ctx.res.body = 'header Content-Type is required'
+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('secure-api', {
+ middleware: [authMiddleware],
+})
```
```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)
+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("secure-api", opts=ApiOptions(middleware=[auth_middleware]))
```
```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
+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)
}
-
- return next(ctx)
- }
}
+
+secureApi := nitric.NewApi("secure-api", apis.WithMiddleware(authMiddleware))
```
```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";
-
+Future authMiddleware(HttpContext ctx) async {
+ if (!ctx.req.headers.containsKey("authorization")) {
+ ctx.res.status = 401;
return ctx;
}
-
return ctx.next();
}
+
+final secureApi = Nitric.api(
+ "secure-api",
+ opts: ApiOptions(middlewares: [authMiddleware])
+);
```
-### API level middleware
+### Security
-Middleware defined at the API level will be called on every request to every route.
+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 } from '@nitric/sdk'
-import { validate, logRequest } from '../middleware'
+import { api, oidcRule } from '@nitric/sdk'
+
+const auth0Rule = oidcRule({
+ name: 'auth0',
+ issuer: 'https://your-tenant.auth0.com',
+ audiences: ['your-api-identifier'],
+})
-const customersApi = api('customers', {
- middleware: [logRequest, validate],
+const secureApi = api('secure-api', {
+ security: [auth0Rule('user.read')],
})
```
```typescript !!
-import { api } from '@nitric/sdk'
-import { validate, logRequest } from '../middleware'
+import { api, oidcRule } from '@nitric/sdk'
-const customersApi = api('customers', {
- middleware: [logRequest, validate],
+const auth0Rule = oidcRule({
+ name: 'auth0',
+ issuer: 'https://your-tenant.auth0.com',
+ audiences: ['your-api-identifier'],
+})
+
+const secureApi = api('secure-api', {
+ security: [auth0Rule('user.read')],
})
```
```python !!
-from nitric.resources import api, ApiOptions
-from common.middleware import validate, log_request
-from nitric.application import Nitric
+from nitric.resources import api, ApiOptions, oidc_rule
-customers_api = api("customers", opts=ApiOptions(middleware=[log_request, validate]))
+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 !!
-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 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",
- apis.WithMiddleware(validate))
-
- nitric.Run()
-}
+secureApi := nitric.NewApi(
+ "secure-api",
+ apis.WithSecurity(auth0Rule([]string{"user.read"})),
+)
```
```dart !!
-import 'package:nitric_sdk/nitric.dart';
-import '../middlewares';
+final auth0Rule = Nitric.oidcRule(
+ "auth0",
+ "https://your-tenant.auth0.com",
+ ["your-api-identifier"]
+);
-final customersApi = Nitric.api(
- "customers",
+final secureApi = Nitric.api(
+ "secure-api",
opts: ApiOptions(
- middlewares: [logRequest, validate],
+ security: [auth0Rule(["user.read"])]
)
);
```
-### 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.
+### Custom Domains
-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.
+You can configure custom domains for your APIs in your stack configuration:
-
+
```yaml title:nitric.prod.yaml
provider: nitric/aws@1.1.0
-region: ap-southeast-2
+region: us-east-1
-# 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
+ my-api:
domains:
- - test.example.com
+ - api.example.com
```
@@ -885,189 +456,96 @@ apis:
```yaml title:nitric.prod.yaml
-# currently unsupported - request support here: https://github.com/nitrictech/nitric/issues
+# 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
+# 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.
+ Custom domains are currently only supported for AWS deployments. See the [AWS
+ Custom Domain Setup](/providers/mappings/aws/apis#custom-domain-prerequisites)
+ section for setup details.
-
- 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.
-
-
-
-## Serving from multiple files
+## Project Organization
-Nitric APIs are scoped to the project and can be referenced from multiple `services`. This allows you to choose the granularity of services that suites your project. For small projects you might have a single service that serves all routes, while in larger projects multiple services might combine to serve paths and methods for your API.
+### Multiple Services
-### Using the same resource name
-
-Since resource names are unique across each Nitric project, you can access a resource in multiple locations by simply reusing it's name. Here's an example of services in different files serving different paths on the same API.
+You can split your API routes across multiple services while using the same API resource:
-```javascript !! title:services/one.js
+```javascript !! title:services/users.js
import { api } from '@nitric/sdk'
-const accountsApi = api('accounts')
+const myApi = api('my-api')
-accountsApi.get('/users/:id', async () => {
- // your logic here
+myApi.get('/users', async (ctx) => {
+ // Handle user listing
})
```
-```typescript !! title:services/one.js
+```typescript !! title:services/users.ts
import { api } from '@nitric/sdk'
-const accountsApi = api('accounts')
+const myApi = api('my-api')
-accountsApi.get('/users/:id', async () => {
- // your logic here
+myApi.get('/users', async (ctx) => {
+ // Handle user listing
})
```
-```python !! title:services/one.py
-from nitric.application import Nitric
-from nitric.context import HttpContext
+```python !! title:services/users.py
from nitric.resources import api
+from nitric.application import Nitric
-accounts_api = api('accounts')
+my_api = api('my-api')
-@accounts_api.get("/users/:id")
-async def get_user(ctx: HttpContext):
- pass # your logic here
+@my_api.get("/users")
+async def list_users(ctx):
+ // Handle user listing
+ pass
Nitric.run()
```
-```go !! title:services/one/main.go
+```go !! title:services/users/main.go
+package main
+
import (
"github.com/nitrictech/go-sdk/nitric"
"github.com/nitrictech/go-sdk/nitric/apis"
)
func main() {
- accountsApi := nitric.NewApi("accounts")
+ myApi := nitric.NewApi("my-api")
- accountsApi.Get("/users/:id", func(ctx *apis.Ctx) {
- // your logic here
+ myApi.Get("/users", func(ctx *apis.Ctx) {
+ // Handle user listing
})
nitric.Run()
}
```
-```dart !! title:services/one.dart
+```dart !! title:services/users.dart
import 'package:nitric_sdk/nitric.dart';
-final accountsApi = Nitric.api("accounts");
+final myApi = Nitric.api("my-api");
-galaxyApi.get("/users/:id", (ctx) async {
- // your logic here
+myApi.get("/users", (ctx) async {
+ // Handle user listing
+ return ctx;
});
```
@@ -1075,246 +553,186 @@ galaxyApi.get("/users/:id", (ctx) async {
-```javascript !! title:services/two.js
+```javascript !! title:services/products.js
import { api } from '@nitric/sdk'
-const accountsApi = api('accounts')
+const myApi = api('my-api')
-accountsApi.get('/orgs/:id', async () => {
- // your logic here
+myApi.get('/products', async (ctx) => {
+ // Handle product listing
})
```
-```typescript !! title:services/two.ts
+```typescript !! title:services/products.ts
import { api } from '@nitric/sdk'
-const accountsApi = api('accounts')
+const myApi = api('my-api')
-accountsApi.get('/orgs/:id', async () => {
- // your logic here
+myApi.get('/products', async (ctx) => {
+ // Handle product listing
})
```
-```python !! title:services/two.py
-from nitric.application import Nitric
-from nitric.context import HttpContext
+```python !! title:services/products.py
from nitric.resources import api
+from nitric.application import Nitric
-accounts_api = api('accounts')
+my_api = api('my-api')
-@accounts_api.get("/orgs/:id")
-async def get_org(ctx: HttpContext):
- pass # your logic here
+@my_api.get("/products")
+async def list_products(ctx):
+ // Handle product listing
+ pass
Nitric.run()
```
-```go !! title:services/two/main.go
+```go !! title:services/products/main.go
+package main
+
import (
"github.com/nitrictech/go-sdk/nitric"
"github.com/nitrictech/go-sdk/nitric/apis"
)
func main() {
- accountsApi := nitric.NewApi("accounts")
+ myApi := nitric.NewApi("my-api")
- accountsApi.Get("/orgs/:id", func(ctx *apis.Ctx) {
- // your logic here
+ myApi.Get("/products", func(ctx *apis.Ctx) {
+ // Handle product listing
})
nitric.Run()
}
```
-```dart !! title:services/two.dart
+```dart !! title:services/products.dart
import 'package:nitric_sdk/nitric.dart';
-final accountsApi = Nitric.api("accounts");
+final myApi = Nitric.api("my-api");
-galaxyApi.get("/users/:id", (ctx) async {
- // your logic here
+myApi.get("/products", (ctx) async {
+ // Handle product listing
+ return ctx;
});
```
-
- Calling `api()` multiple times with the same name returns the same API
- resource each time, allowing it to be referenced in multiple services.
-
-
-### Importing an existing resource
-
-While reusing a name is useful, it can lead to errors due to typos or when the configuration of the resource is complex. For this reason it's often preferable to declare the resource in a shared location and import it into the services as needed.
+### Shared Resources
-Here is the same example enhanced to import a common API resource.
+For better organization, you can define shared API resources:
-```javascript !! title:resources/index.js
+```javascript !! title:resources/api.js
import { api } from '@nitric/sdk'
-export const accountsApi = api('accounts')
+export const myApi = api('my-api')
```
-```typescript !! title:resources/index.ts
+```typescript !! title:resources/api.ts
import { api } from '@nitric/sdk'
-export const accountsApi = api('accounts')
+export const myApi = api('my-api')
```
-```python !! title:common/resources.py
+```python !! title:resources/api.py
from nitric.resources import api
-accounts_api = api('accounts')
+my_api = api('my-api')
```
-```go !! title:resources/main.go
+```go !! title:resources/api/main.go
+package api
+
import (
"github.com/nitrictech/go-sdk/nitric"
"github.com/nitrictech/go-sdk/nitric/apis"
)
-var AccountsApi apis.Api
+var MyApi apis.Api
func init() {
- accountsApi := nitric.NewApi("accounts")
- AccountsApi = accountsApi
+ MyApi = nitric.NewApi("my-api")
}
```
-```dart !! title:common/resources.dart
+```dart !! title:resources/api.dart
import 'package:nitric_sdk/nitric.dart';
-final accountsApi = Nitric.api("accounts");
+final myApi = Nitric.api("my-api");
```
-
-
-```javascript !! title:services/one.js
-import { accountsApi } from '../resources'
-
-accountsApi.get('/users/:id', async () => {
- // your logic here
-})
-```
-
-```typescript !! title:services/one.ts
-import { accountsApi } from '../resources'
-
-accountsApi.get('/users/:id', async () => {
- // your logic here
-})
-```
-
-```python !! title:services/one.py
-from nitric.application import Nitric
-from nitric.context import HttpContext
-from common.resources import accounts_api
-
-
-@accounts_api.get("/users/:id")
-async def get_user(ctx: HttpContext):
- pass # your logic here
-
-Nitric.run()
-```
-
-```go !! title:services/one/main.go
-package main
-
-import (
- "your/resources"
-
- "github.com/nitrictech/go-sdk/nitric"
- "github.com/nitrictech/go-sdk/nitric/apis"
-)
-
-func main() {
- resources.AccountsApi.Get("/users/:id", func(ctx *apis.Ctx) {
- // your logic here
- })
-
- nitric.Run()
-}
-```
-
-```dart !! title:services/one.dart
-import '../resources';
-
-accountsApi.get("/users/:id", (ctx) async {
- // your logic here
-});
-```
-
-
+Then import and use them in your services:
-```javascript !! title:services/two.js
-import { accountsApi } from '../resources'
+```javascript !! title:services/users.js
+import { myApi } from '../resources/api'
-accountsApi.get('/orgs/:id', async () => {
- // your logic here
+myApi.get('/users', async (ctx) => {
+ // Handle user listing
})
```
-```typescript !! title:services/two.ts
-import { accountsApi } from '../resources'
+```typescript !! title:services/users.ts
+import { myApi } from '../resources/api'
-accountsApi.get('/orgs/:id', async () => {
- // your logic here
+myApi.get('/users', async (ctx) => {
+ // Handle user listing
})
```
-```python !! title:services/two.py
+```python !! title:services/users.py
from nitric.application import Nitric
-from nitric.context import HttpContext
-from common.resources import accounts_api
-
+from resources.api import my_api
-@accounts_api.get("/orgs/:id")
-async def get_org(ctx: HttpContext):
- pass # your logic here
+@my_api.get("/users")
+async def list_users(ctx):
+ // Handle user listing
+ pass
Nitric.run()
```
-```go !! title:services/two/main.go
+```go !! title:services/users/main.go
package main
import (
- "your/resources"
-
+ "your-project/resources/api"
"github.com/nitrictech/go-sdk/nitric"
"github.com/nitrictech/go-sdk/nitric/apis"
)
func main() {
- resources.AccountsApi.Get("/orgs/:id", func(ctx *apis.Ctx) {
- // your logic here
+ api.MyApi.Get("/users", func(ctx *apis.Ctx) {
+ // Handle user listing
})
nitric.Run()
}
```
-```dart !! title:services/two.dart
-import '../resources';
+```dart !! title:services/users.dart
+import '../resources/api.dart';
-accountsApi.get("/orgs/:id", (ctx) async {
- // your logic here
+myApi.get("/users", (ctx) async {
+ // Handle user listing
+ return ctx;
});
```
-## Cloud Service Mapping
+## Cloud Provider Support
-Each cloud provider comes with a set of default services used when deploying resources. You can find the default services for each cloud provider below.
+Each cloud provider has specific features and limitations for API deployments:
- [AWS](/providers/mappings/aws/apis)
- [Azure](/providers/mappings/azure/apis)
- [Google Cloud](/providers/mappings/gcp/apis)
+
+Security rules are not enforced during local development.
diff --git a/docs/providers/mappings/aws/apis.mdx b/docs/providers/mappings/aws/apis.mdx
index 2ea6fe7ea..7fe45eb71 100644
--- a/docs/providers/mappings/aws/apis.mdx
+++ b/docs/providers/mappings/aws/apis.mdx
@@ -28,3 +28,32 @@ During deployment the Nitric CLI builds your API's routes, methods and handlers:
- Lambda ARNs are injected into the API definition using the [x-amazon-apigateway-integration object](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html), creating an [API Gateway Integration](https://docs.aws.amazon.com/apigateway/api-reference/resource/integration/) for each.
- The API definition is deployed as an API Gateway v2 HTTP API, using the `$default` stage name
- IAM policies are created enabling API Gateway to execute the Lambdas
+
+### 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:
+
+1. Navigate to Route 53 in the AWS Console
+2. Select 'hosted zones' from the left navigation
+3. Click 'Create hosted zone'
+4. Enter your domain name and choose the 'Public hosted zone' type.
+5. Click 'Create hosted zone'
+6. You will now be provided with a set of NS DNS records to configure in the DNS provider for your domain
+7. 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.
+