← API

API FEATURE

REST + GraphQL

Semitexa lets one use case answer both transports, so teams do not have to choose between REST and GraphQL too early.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

A single Semitexa use case can answer both REST and GraphQL. The public transports differ, but the business flow stays in one place.

How it works

An existing REST payload opts into GraphQL with #[ExposeAsGraphql(...)]. REST clients keep calling the normal HTTP route, GraphQL clients call POST /graphql, and both transports reuse the same application contract.

Why it matters

This matters when products are not ready to choose one public style forever. Semitexa lets teams support both without forking their use case into two implementations that drift apart over time.

Key concepts

REST + GraphQL
One use case exposed through two transports without duplicated handler logic.
shared contract
The same payload and output boundary stays authoritative across both transports.
transport split
REST and GraphQL can differ at the edge while still sharing the same application execution path.

REST + GraphQL

One Semitexa use case, two transports

These fields come from real REST payloads that also opt into GraphQL. REST clients keep their normal HTTP routes, GraphQL clients call POST /graphql, and the use case stays in one place.

Shared contract

Real GraphQL fields derived from the current REST API

query productBySlug

Derived GraphQL field for loading one product by slug from the Semitexa demo catalog.

Already have
GET /demo/api/v1/products/{slug}
GraphQL result
query.productBySlug
Returns
ProductGraphqlView
query {
  productBySlug {
    slug
    name
    price
    category {
      name
    }
  }
}
query products

Derived GraphQL field for paginated product collection reads from the Semitexa demo catalog.

Already have
GET /demo/api/v1/products
GraphQL result
query.products
Returns
ProductListGraphqlView
query {
  products {
    items {
      slug
      name
      price
    }
    total
    page
    limit
  }
}

Pseudo SDL

What GraphQL can expose from the same use case

type Query {
  productBySlug: ProductGraphqlView
  products: ProductListGraphqlView
}
REST + GraphQL #[ExposeAsGraphql] shared use case no duplicated logic

Verified against Semitexa Ultimate 2026.09.19.1020

REST + GraphQL

Semitexa lets one use case answer both transports, so teams do not have to choose between REST and GraphQL too early.

How it works

A Payload DTO marked with both #[ExternalApi] and #[ExposeAsGraphql] registers the same operation into both the REST route table and the GraphQL schema. The handler is written once. The framework dispatches the same handler for both an HTTP GET request and a POST /graphql query targeting the same field. The output DTO is the same in both cases.

Why this matters

Transport-level decisions are often made before the product is stable. Semitexa's dual-transport model delays that commitment: you write one handler now and surface it as REST or GraphQL or both later. The alternative — maintaining separate handler and resolver trees for each transport — creates divergence that grows with every new field or behavior change.

© Jeff Sickel: "Deleted code is debugged code."

REST + GraphQL Payload Trusted input boundary
<?phpdeclare(strict_types=1);namespace App\Api\Product;use App\Api\Product\ProductApiResponse;use App\Api\Product\ProductGraphqlView;use Semitexa\Api\Attribute\ExternalApi;use Semitexa\Core\Attribute\AsPublicPayload;use Semitexa\Graphql\Attribute\ExposeAsGraphql;#[AsPublicPayload(path: '/api/v1/products/{slug}', methods: ['GET'], responseWith: ProductApiResponse::class)]#[ExternalApi(version: 'v1')]#[ExposeAsGraphql(    field: 'productBySlug',    rootType: 'query',    output: ProductGraphqlView::class,)]final class ProductDetailPayload{    protected string $slug = '';    public function getSlug(): string { return $this->slug; }    public function setSlug(string $slug): void { $this->slug = trim($slug); }}

REST + GraphQL

What this combined mode is proving

What already exists What you add What you avoid
A live REST contract: GET /demo/api/v1/products/{slug} One GraphQL declaration for query.productBySlug Duplicating the same use case in separate resolver logic.
A live REST contract: GET /demo/api/v1/products One GraphQL declaration for query.products Duplicating the same use case in separate resolver logic.

How it works

An existing REST payload opts into GraphQL with #[ExposeAsGraphql(...)]. REST clients keep calling the normal HTTP route, GraphQL clients call POST /graphql, and both transports reuse the same application contract.

Why it matters

This matters when products are not ready to choose one public style forever. Semitexa lets teams support both without forking their use case into two implementations that drift apart over time.

Key concepts

REST + GraphQL
One use case exposed through two transports without duplicated handler logic.
shared contract
The same payload and output boundary stays authoritative across both transports.
transport split
REST and GraphQL can differ at the edge while still sharing the same application execution path.

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal