

# Build a server route with the SDK [#build-a-server-route-with-the-sdk]

Use this guide when your application owns the HTTP route and only needs
TaxKit for the calculation.

## Before you start [#before-you-start]

* Use a server-side TypeScript runtime.
* Install the SDK with [Install the SDK](/start/install-the-sdk).
* Keep request validation at your application boundary.

## Steps [#steps]

1. Parse your application request.
2. Convert accepted fields into canonical TaxKit facts.
3. Call the SDK helper.
4. Return the report fields your application needs.

## Example [#example]

```ts
import { PublicCalculatorServiceLive } from "@taxkit/calculators/live";
import { CalculationEngineLive } from "@taxkit/core";
import { Cents, Money } from "@taxkit/core/primitives";
import { GrossPay } from "@taxkit/rules-au-pay";
import { AuPayTakeHomeCalculation } from "@taxkit/sdk/au/effect";
import { calculateReport } from "@taxkit/sdk/effect";
import { Effect, Layer, Schema } from "effect";
import * as HttpServerRequest from "effect/http/HttpServerRequest";

class PayPreviewRequestError extends Schema.TaggedError<PayPreviewRequestError>()(
  "PayPreviewRequestError",
  { message: Schema.String }
) {}

const PayPreviewRequest = Schema.Struct({
  grossPayCents: Cents,
  period: GrossPay.fields.period,
  taxFreeThresholdClaimed: Schema.Boolean,
});

const PayPreviewResponse = Schema.Struct({
  netPayCents: Cents,
  withholdingsCents: Cents,
});

const TaxKitLayer = PublicCalculatorServiceLive.pipe(
  Layer.provide(CalculationEngineLive)
);

export const handlePayPreview = (request: Request) =>
  Effect.gen(function* () {
    const body = yield* HttpServerRequest.fromWeb(request).text.pipe(
      Effect.flatMap(
        Schema.decodeEffect(Schema.fromJsonString(PayPreviewRequest))
      ),
      Effect.mapError(
        () =>
          new PayPreviewRequestError({
            message: "Invalid pay preview request.",
          })
      )
    );
    const report = yield* calculateReport(AuPayTakeHomeCalculation, {
      grossPay: new GrossPay({
        amount: new Money({ cents: body.grossPayCents, currency: "AUD" }),
        period: body.period,
      }),
      taxFreeThresholdClaimed: body.taxFreeThresholdClaimed,
    });
    const response = yield* Schema.encodeEffect(
      Schema.fromJsonString(PayPreviewResponse)
    )({
      netPayCents: report.netPay.cents,
      withholdingsCents: report.withholdingsTotal.cents,
    });
    return new Response(response, {
      headers: { "content-type": "application/json" },
    });
  }).pipe(Effect.provide(TaxKitLayer));
```

## Verify the result [#verify-the-result]

Request body:

```json
{
  "grossPayCents": 165400,
  "period": "weekly",
  "taxFreeThresholdClaimed": true
}
```

Expected response:

```json
{ "netPayCents": 130100, "withholdingsCents": 35300 }
```

## Handle errors [#handle-errors]

The handler returns an Effect. Your server host owns execution and maps the
typed failure channel to its HTTP status and response shape. Invalid JSON,
fractional cents and unsupported periods fail request validation before the
calculator runs. Provider or request details are not included in the safe
request error.

## Related pages [#related-pages]

* [Plain SDK](/sdk/plain-sdk)
* [Build a browser app with the HTTP API](/guides/build-a-browser-app-with-the-http-api)
* [SDK, HTTP API and calculators service boundary](/concepts/sdk-http-api-and-calculators-service-boundary)
