Website | Source | Docs (2.0) | Examples

Quick Start

Shapes and traits

Smithy models consist of shapes and traits. Shapes are instances of types. Traits are used to add more information to shapes that might be useful for clients, servers, or documentation.

Smithy supports the following types:

Type Description
blob Uninterpreted binary data
boolean Boolean value type
string UTF-8 encoded string
byte 8-bit signed integer ranging from -128 to 127 (inclusive)
short 16-bit signed integer ranging from -32,768 to 32,767 (inclusive)
integer 32-bit signed integer ranging from -2^31 to (2^31)-1 (inclusive)
long 64-bit signed integer ranging from -2^63 to (2^63)-1 (inclusive)
float Single precision IEEE-754 floating point number
double Double precision IEEE-754 floating point number
bigInteger Arbitrarily large signed integer
bigDecimal Arbitrary precision signed decimal number
timestamp An instant in time with no UTC offset or timezone.
document An untyped JSON-like value.
List Homogeneous collection of values
Map Map data structure that maps string keys to homogeneous values
Structure Fixed set of named heterogeneous members
Union Tagged union data structure that can take on several different, but fixed, types
Service Entry point of an API that aggregates resources and operations together
Operation Represents the input, output and possible errors of an API operation
Resource An entity with an identity, set of operations, and child resources

Defining resources

A resource is contained within a service or another resource. Resources have identifiers, operations, and any number of child resources.

model/weather.smithy

$version: "2"
namespace example.weather

/// Provides weather forecasts.
service Weather {
    version: "2006-03-01"
    resources: [
        City
    ]
}

resource City {
    identifiers: { cityId: CityId }
    read: GetCity
    list: ListCities
}

// "pattern" is a trait.
@pattern("^[A-Za-z0-9 ]+$")
string CityId

Because the Weather service contains many cities, the City resource defines an identifier. Identifiers are used to refer to a specific resource within a service. The "identifiers" property is a mapping of identifier names to the shape to use for that identifier. If the input structure of an operation uses the same names and targeted shapes as the identifiers property of the resource, the structure is automatically configured to work with the resource so that input members of the operation are used to provide the identity of the resource.

Each City has a single Forecast. This can be defined by adding the Forecast resource to the resources property of the City resource.

resource City {
    identifiers: { cityId: CityId }
    read: GetCity
    list: ListCities
    resources: [
        Forecast
    ]
}

resource Forecast {
    identifiers: { cityId: CityId }
    read: GetForecast
}

Child resources must define the exact same identifiers property of their parent, but they are allowed to add any number of additional identifiers if needed. Because there is only one forecast per city, no additional identifiers were added to the identifiers property that isn't present on the City resource.

The state of a resource is represented through its properties. City contains coordinates, and Forecast has a chance of rain represented as a float. Input and output members of resource operations map to resource properties or identifiers to perform updates on or examine the state of a resource.

model/weather.smithy

resource City {
    identifiers: { cityId: CityId }
    properties: { coordinates: CityCoordinates }
    read: GetCity
    list: ListCities
    resources: [
        Forecast
    ]
}

structure CityCoordinates {
    @required
    latitude: Float

    @required
    longitude: Float
}


structure GetCityOutput for City {
    $coordinates
}

resource Forecast {
    identifiers: { cityId: CityId }
    properties: { chanceOfRain: Float }
    read: GetForecast
}

structure GetForecastOutput for Forecast {
    $chanceOfRain
}

Defining operations

The put, create, read, update, delete, and list properties of a resource are used to define the lifecycle operations of a resource. Lifecycle operations are the canonical methods used to read and transition the state of a resource using well-defined semantics. Defining lifecycle operations helps automated tooling reason about your API.

Let's define the operation used to "read" a City.

model/weather.smithy

@readonly
operation GetCity {
    input := for City {
        // "cityId" provides the identifier for the resource and
        // has to be marked as required.
        @required
        $cityId
    }

    output := for City {
        // "required" is used on output to indicate if the service
        // will always provide a value for the member.
        // "notProperty" indicates that top-level input member "name"
        // is not bound to any resource property.
        @required
        @notProperty
        name: String

        @required
        $coordinates
    }

    errors: [
        NoSuchResource
    ]
}

// "error" is a trait that is used to specialize
// a structure as an error.
@error("client")
structure NoSuchResource {
    @required
    resourceType: String
}

And define the operation used to "read" a Forecast.

model/weather.smithy

@readonly
operation GetForecast {
    // "cityId" provides the only identifier for the resource since
    // a Forecast doesn't have its own.
    input := for Forecast {
        @required
        $cityId
    }

    output := for Forecast {
        $chanceOfRain
    }
}

Review

Modeling a service should be easy, no matter the interface.

Model

Build APIs your customers will love using the Smithy Interface Definition Language (IDL).

The Smithy IDL provides an intuitive syntax that codifies best practices learned from years of experience building services and SDKs in over a dozen programming languages.

Use Smithy's extensible model validation tools to ensure the quality and consistency of your APIs. Customizable linting, validation, and backwards-compatibility checks integrate with your IDE and CI/CD pipelines so you catch API quality issues before your customers do.

Build

Smithy's build tool integrations and plugin system make it easy to get started generating code from a Smithy model. Use one of the many open-source plugins for Smithy or create your own to make everything from model diagrams to SDKs.

Write your API model once and generate clients, servers, and documentation for multiple programming languages with Smithy's CLI.

Example: Full-Stack Application

Models

main.smithy

$version: "2.0"

namespace com.example

use aws.protocols#restJson1
use smithy.framework#ValidationException

/// Allows users to retrieve a menu, create a coffee order, and
/// and to view the status of their orders
@title("Coffee Shop Service")
@restJson1
service CoffeeShop {
    version: "2024-08-23"
    operations: [
        GetMenu
    ]
    resources: [
        Order
    ]
    errors: [
        ValidationException
    ]
}

/// Retrieve the menu
@http(method: "GET", uri: "/menu")
@readonly
operation GetMenu {
    output := {
        items: CoffeeItems
    }
}

coffee.smithy

$version: "2.0"

namespace com.example

/// An enum describing the types of coffees available
enum CoffeeType {
    DRIP
    POUR_OVER
    LATTE
    ESPRESSO
    COLD_BREW
}

/// A structure which defines a coffee item which can be ordered
structure CoffeeItem {
    @required
    type: CoffeeType

    @required
    description: String
}

/// A list of coffee items
list CoffeeItems {
    member: CoffeeItem
}

order.smithy

$version: "2.0"

namespace com.example

/// An Order resource, which has an id and descibes an order by the type of coffee
/// and the order's status
resource Order {
    identifiers: {
        id: Uuid
    }
    properties: {
        coffeeType: CoffeeType
        status: OrderStatus
    }
    read: GetOrder
    create: CreateOrder
}

/// Create an order
@idempotent
@http(method: "PUT", uri: "/order")
operation CreateOrder {
    input := for Order {
        @required
        $coffeeType
    }

    output := for Order {
        @required
        $id

        @required
        $coffeeType

        @required
        $status
    }
}

/// Retrieve an order
@readonly
@http(method: "GET", uri: "/order/{id}")
operation GetOrder {
    input := for Order {
        @httpLabel
        @required
        $id
    }

    output := for Order {
        @required
        $id

        @required
        $coffeeType

        @required
        $status
    }

    errors: [
        OrderNotFound
    ]
}

/// An error indicating an order could not be found
@httpError(404)
@error("client")
structure OrderNotFound {
    message: String
    orderId: Uuid
}

/// An identifier to describe a unique order
@length(min: 1, max: 128)
@pattern("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
string Uuid

/// An enum describing the status of an order
enum OrderStatus {
    IN_PROGRESS
    COMPLETED
}

Server implementation

index.ts

import { getCoffeeShopServiceHandler } from "@com.example/coffee-shop-server";
import { IncomingMessage, ServerResponse, createServer } from "http";
import { convertRequest, writeResponse } from "@aws-smithy/server-node";
import { CoffeeShop } from "./CoffeeShop";


// Instantiate our coffee service implementation
const coffeeService = new CoffeeShop();
// Create a service handler using our coffee service
const serviceHandler = getCoffeeShopServiceHandler(coffeeService);
// The coffee shop context object
const ctx = { orders: new Map(), queue: [] };

// Create the node server with the service handler
const server = createServer(async function (
  req: IncomingMessage,
  res: ServerResponse<IncomingMessage> & { req: IncomingMessage }
) {
  // Convert NodeJS's http request to an HttpRequest.
  const httpRequest = convertRequest(req);

  // Call the service handler, which will route the request to the GreetingService
  // implementation and then serialize the response to an HttpResponse.
  const httpResponse = await serviceHandler.handle(httpRequest, ctx);

  // Write the HttpResponse to NodeJS http's response expected format.
  return writeResponse(httpResponse, res);
});

const port = 3001
server.listen(port);
console.log(`Started server on port ${port}...`);

// Asynchronously handle orders as they come in
coffeeService.handleOrders(ctx)

CoffeeShop.ts

import { CoffeeShopService, CoffeeType, CreateOrderServerInput, CreateOrderServerOutput, GetMenuServerInput, GetMenuServerOutput, GetOrderServerInput, GetOrderServerOutput, OrderNotFound, OrderStatus } from "@com.example/coffee-shop-server";
import { randomUUID } from "crypto";

// A context object for holding state in our service
export interface CoffeeShopContext {
    // A map for storing order information
    orders: Map<string, OrderData>;
    // An order queue for handling orders
    queue: OrderData[];
}

// An implementation of the service from the SSDK
export class CoffeeShop implements CoffeeShopService<CoffeeShopContext> {

    async CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): Promise<CreateOrderServerOutput> {
        console.log("received an order request...")
        const order = {
            orderId: randomUUID(),
            coffeeType: input.coffeeType,
            status: OrderStatus.IN_PROGRESS
        }

        context.orders.set(order.orderId, order)
        context.queue.push(order)

        console.log(`created order: ${JSON.stringify(order)}`)
        return {
            id: order.orderId,
            coffeeType: order.coffeeType,
            status: order.status
        }
    }

    async GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): Promise<GetMenuServerOutput> {
        console.log("getting menu...")
        return {
            items: [
                {
                    type: CoffeeType.DRIP,
                    description: "A clean-bodied, rounder, and more simplistic flavour profile.\n" +
                        "Often praised for mellow and less intense notes.\n" +
                        "Far less concentrated than espresso."
                },
                {
                    type: CoffeeType.POUR_OVER,
                    description: "Similar to drip coffee, but with a process that brings out more subtle nuances in flavor.\n" +
                        "More concentrated than drip, but less than espresso."
                },
                {
                    type: CoffeeType.LATTE,
                    description: "A creamier, milk-based drink made with espresso.\n" +
                        "A subtle coffee taste, with smooth texture.\n" +
                        "High milk-to-coffee ratio."
                },
                {
                    type: CoffeeType.ESPRESSO,
                    description: "A highly concentrated form of coffee, brewed under high pressure.\n" +
                        "Syrupy, thick liquid in a small serving size.\n" +
                        "Full bodied and intensely aromatic."
                },
                {
                    type: CoffeeType.COLD_BREW,
                    description: "A high-extraction and chilled form of coffee that has been cold-pressed..\n" +
                        "Different flavor profile than other hot methods of brewing.\n" +
                        "Smooth and slightly more caffeinated as a result of its concentration."
                }
            ]
        }
    }

    async GetOrder(input: GetOrderServerInput, context: CoffeeShopContext): Promise<GetOrderServerOutput> {
        console.log(`getting an order (${input.id})...`)
        if (context.orders.has(input.id)) {
            const order = context.orders.get(input.id)
            return {
                id: order.orderId,
                coffeeType: order.coffeeType,
                status: order.status
            }
        } else {
            console.log(`order (${input.id}) does not exist.`)
            throw new OrderNotFound({
                message: `order ${input.id} not found.`,
                orderId: input.id
            })
        }
    }

    // Handle orders as they come in (FIFO), marking them completed based on some random
    // timing (to simulate a delay)
    async handleOrders(context: CoffeeShopContext) {
        console.log("handling orders...")
        while (true) {
            await new Promise(resolve => setTimeout(resolve, Math.random() * 1000 + 1000));
            let order = context.queue.shift()
            if (order != null) {
                order.status = OrderStatus.COMPLETED
                console.log(`order ${order.orderId} is completed.`)  
            }
        }
    }
}

// A data object to hold order data
interface OrderData {
    orderId: string
    coffeeType: CoffeeType;
    status: OrderStatus;
}

Build tools

Code Generators

Client Code Generators

Server Code Generators

Learning resources

IDE Support

Implementations

Model Converters

CICD Support

GitHub Actions

Videos

Blog Posts

Others


Tags: distribution   dsl   language  

Last modified 23 August 2025