← Back to Documentation

HTTP Services

ARO provides built-in HTTP server and client capabilities. This chapter covers how to build web APIs and make HTTP requests.

HTTP Server

Starting the Server

Start an HTTP server in Application-Start:

(Application-Start: Web API) {
    <Start> the <http-server> on port 8080.
    <Keepalive> the <application> for the <events>.
    <Return> an <OK: status> for the <startup>.
}

Route Handlers (Contract-First)

Define routes in openapi.yaml and implement feature sets named after operationIds:

(listUsers: User API) {
    <Retrieve> the <users> from the <user-repository>.
    <Return> an <OK: status> with <users>.
}

(createUser: User API) {
    <Extract> the <user-data> from the <request: body>.
    <Create> the <user> with <user-data>.
    <Store> the <user> into the <user-repository>.
    <Return> a <Created: status> with <user>.
}

(updateUser: User API) {
    <Extract> the <user-id> from the <pathParameters: id>.
    <Extract> the <updates> from the <request: body>.
    <Retrieve> the <user> from the <user-repository> where id = <user-id>.
    <Transform> the <updated-user> from the <user> with <updates>.
    <Store> the <updated-user> into the <user-repository>.
    <Return> an <OK: status> with <updated-user>.
}

(deleteUser: User API) {
    <Extract> the <user-id> from the <pathParameters: id>.
    <Delete> the <user> from the <user-repository> where id = <user-id>.
    <Return> a <NoContent: status> for the <deletion>.
}

Request Data

Access request data using qualified variables:

(createUser: User API) {
    (* Path parameters *)
    <Extract> the <id> from the <pathParameters: id>.

    (* Query string *)
    <Extract> the <page> from the <queryParameters: page>.
    <Extract> the <limit> from the <queryParameters: limit>.

    (* Request body *)
    <Extract> the <data> from the <request: body>.

    (* Headers *)
    <Extract> the <auth-token> from the <request: headers Authorization>.
    <Extract> the <content-type> from the <request: headers Content-Type>.

    ...
}

Response Status Codes

Return appropriate status codes:

(* 2xx Success *)
<Return> an <OK: status> with <data>.           (* 200 *)
<Return> a <Created: status> with <resource>.   (* 201 *)
<Return> an <Accepted: status> for <async>.     (* 202 *)
<Return> a <NoContent: status> for <deletion>.  (* 204 *)

(* 4xx Client Errors *)
<Return> a <BadRequest: status> with <errors>.       (* 400 *)
<Return> an <Unauthorized: status> for <auth>.       (* 401 *)
<Return> a <Forbidden: status> for <access>.         (* 403 *)
<Return> a <NotFound: status> for <missing>.         (* 404 *)

(* 5xx Server Errors *)
<Return> an <InternalError: status> for <error>.     (* 500 *)

RESTful Patterns

Collection Resource

(* List all *)
(listProducts: Product API) {
    <Extract> the <page> from the <queryParameters: page>.
    <Extract> the <limit> from the <queryParameters: limit>.

    <Retrieve> the <products> from the <product-repository>
        with pagination <page> <limit>.

    <Return> an <OK: status> with <products>.
}

(* Create new *)
(createProduct: Product API) {
    <Extract> the <product-data> from the <request: body>.
    <Validate> the <product-data> for the <product-schema>.
    <Create> the <product> with <product-data>.
    <Store> the <product> into the <product-repository>.
    <Return> a <Created: status> with <product>.
}

Single Resource

(* Get one *)
(getProduct: Product API) {
    <Extract> the <product-id> from the <pathParameters: id>.
    <Retrieve> the <product> from the <product-repository> where id = <product-id>.

    if <product> is empty then {
        <Return> a <NotFound: status> for the <missing: product>.
    }

    <Return> an <OK: status> with <product>.
}

(* Delete one *)
(deleteProduct: Product API) {
    <Extract> the <product-id> from the <pathParameters: id>.
    <Retrieve> the <product> from the <product-repository> where id = <product-id>.

    if <product> is empty then {
        <Return> a <NotFound: status> for the <missing: product>.
    }

    <Delete> the <product> from the <product-repository> where id = <product-id>.
    <Return> a <NoContent: status> for the <deletion>.
}

Nested Resources

(* User's orders *)
(listUserOrders: Order API) {
    <Extract> the <user-id> from the <pathParameters: userId>.
    <Retrieve> the <orders> from the <order-repository> where userId = <user-id>.
    <Return> an <OK: status> with <orders>.
}

(* Specific order for user *)
(getUserOrder: Order API) {
    <Extract> the <user-id> from the <pathParameters: userId>.
    <Extract> the <order-id> from the <pathParameters: orderId>.
    <Retrieve> the <order> from the <order-repository>
        where id = <order-id> and userId = <user-id>.

    if <order> is empty then {
        <Return> a <NotFound: status> for the <missing: order>.
    }

    <Return> an <OK: status> with <order>.
}

HTTP Client

Making Requests

Make outgoing HTTP requests:

(* Simple GET *)
<Request> the <data> from "https://api.example.com/resource".

(* GET with response handling *)
<Request> the <response> from "https://api.example.com/users".
<Extract> the <users> from the <response: body>.

Request Methods

(* GET *)
<Request> the <data> from <UserAPI: GET /users>.

(* POST *)
<Call> the <result> via <UserAPI: POST /users> with <user-data>.

(* PUT *)
<Call> the <updated> via <UserAPI: PUT /users/{id}> with <updates>.

(* DELETE *)
<Call> the <result> via <UserAPI: DELETE /users/{id}>.

Response Handling

(Fetch External Data: External Service) {
    <Request> the <response> from "https://api.example.com/data".

    <Extract> the <status-code> from the <response: statusCode>.
    <Extract> the <body> from the <response: body>.

    if <status-code> is not 200 then {
        <Log> "API error: ${status-code}" to the <console>.
        <Return> a <BadRequest: status> for the <api: error>.
    }

    <Return> an <OK: status> with <body>.
}

Authentication

API Key

(getSecureData: Secure API) {
    <Extract> the <api-key> from the <request: headers X-API-Key>.

    when <api-key> is empty {
        <Return> an <Unauthorized: status> for the <missing: api-key>.
    }

    <Validate> the <api-key> for the <api-key-registry>.

    if <validation> is failed then {
        <Return> an <Unauthorized: status> for the <invalid: api-key>.
    }

    <Retrieve> the <data> from the <repository>.
    <Return> an <OK: status> with <data>.
}

Bearer Token

(getProtectedResource: Protected API) {
    <Extract> the <auth-header> from the <request: headers Authorization>.

    when <auth-header> is empty {
        <Return> an <Unauthorized: status> for the <missing: token>.
    }

    <Parse> the <token> from the <auth-header> as "Bearer".
    <Validate> the <token> for the <jwt-validator>.

    if <token> is invalid then {
        <Return> an <Unauthorized: status> for the <invalid: token>.
    }

    <Extract> the <user-id> from the <token: claims userId>.
    <Return> an <OK: status> with <data>.
}

Best Practices

Validate Input

(createUser: User API) {
    <Extract> the <user-data> from the <request: body>.

    (* Always validate input *)
    <Validate> the <user-data> for the <user-schema>.

    if <validation> is failed then {
        <Return> a <BadRequest: status> with <validation: errors>.
    }

    <Create> the <user> with <user-data>.
    <Return> a <Created: status> with <user>.
}

Use Appropriate Status Codes

(* Be specific with status codes *)
<Return> a <Created: status> with <new-resource>.    (* Not just OK *)
<Return> a <NoContent: status> for the <deletion>.   (* Not OK with empty body *)
<Return> a <NotFound: status> for the <missing>.    (* Not BadRequest *)

Next Steps

Contract-First Development - OpenAPI integration
Sockets - TCP communication