Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BrandoGraphql

GraphQL API layer for Brando CMS blueprints via Absinthe.

Auto-generates Absinthe types, queries, and resolvers from your existing Brando blueprints. No hand-written schemas — add a graphql DSL block and you get a fully functional GraphQL endpoint.

Installation

Add brando_graphql to your dependencies:

# mix.exs
defp deps do
  [
    {:brando_graphql, github: "brandocms/brando_graphql"}
  ]
end

Setup

1. Add the extension to your blueprint

defmodule MyApp.Projects.Project do
  use Brando.Blueprint,
    application: "MyApp",
    domain: "Projects",
    schema: "Project",
    singular: "project",
    plural: "projects",
    extensions: [BrandoGraphql.Resource]

  # ... attributes, assets, relations, forms, listings ...

  graphql do
    type_name :project

    queries do
      list :list_projects
      get :get_project
    end

    hide_fields [:creator_id, :deleted_at, :marked_as_deleted]
  end
end

2. Create the schema module

A single module aggregates all your blueprint types into one Absinthe schema:

defmodule MyAppWeb.API.Schema do
  use BrandoGraphql.Schema,
    schemas: [
      MyApp.Projects.Project,
      MyApp.Projects.Client
    ]
end

This auto-generates:

  • An Absinthe object type for each blueprint (from attributes, assets, and relations)
  • List and get query fields with resolvers that delegate to your existing context functions
  • Custom types for Brando images, videos, files, and galleries

3. Add the route

# router.ex
scope "/api" do
  pipe_through :api

  forward "/graphql", Absinthe.Plug,
    schema: MyAppWeb.API.Schema,
    json_codec: Jason
end

That's it. Start your server and query away.

Usage

List query

{
  projects(limit: 10, offset: 0, status: "published") {
    entries {
      id
      title
      slug
      insertedAt
    }
    paginationMeta {
      totalEntries
      totalPages
      currentPage
      pageSize
    }
  }
}

List queries return a wrapper with entries and paginationMeta.

Single item query

{
  project(id: "1") {
    id
    title
    slug
    status
    listingImage {
      url
      width
      height
      alt
      dominantColor
      sizes
    }
    projectGallery {
      galleryObjects {
        sequence
        image { url width height }
        video { url width height }
      }
    }
  }
}

Sorting

Pass an order string argument. Prefix with - for descending:

{
  projects(order: "-inserted_at,title") {
    entries { id title }
  }
}

Filtering

Pass a JSON-encoded string to the filter argument:

# Inline
{
  projects(filter: "{\"language\": \"en\"}") {
    entries { id title }
    paginationMeta { totalEntries }
  }
}

With variables (recommended — cleaner for frontends):

query($filter: String) {
  projects(filter: $filter) {
    entries { id title }
  }
}
{ "filter": "{\"language\": \"en\"}" }

Introspection

Standard GraphQL introspection works out of the box:

{
  __type(name: "Project") {
    fields {
      name
      type { name kind }
    }
  }
}

DSL Reference

graphql block

Option Type Default Description
type_name :atom derived from blueprint singular Absinthe object type name
hide_fields [:atom] [] Fields to exclude from the GraphQL type

queries block

Entity Argument Description
list :action Context function for listing (e.g. :list_projects)
get :action Context function for a single entry (e.g. :get_project)

The context function names must match functions defined in your blueprint's context module (the same ones used by your admin interface).

Query arguments

List queries accept:

Argument Type Default Description
limit Int 25 Max entries to return
offset Int 0 Number of entries to skip
order String Comma-separated fields, - prefix for desc
status String "published" Filter by status
filter String JSON-encoded filter object, e.g. "{\"language\": \"en\"}"

Get queries accept:

Argument Type Required Description
id ID! yes Entry ID

Type mapping

Attributes

Brando type GraphQL type
:string, :text, :slug, :villain, :status, :enum, :language String
:integer Int
:float Float
:boolean Boolean
:datetime, :naive_datetime NaiveDateTime
:date Date
:json, :map JSON (custom scalar)

Assets

Brando asset GraphQL type Key fields
:image BrandoImage url, path, width, height, alt, title, credits, dominantColor, sizes, focal, formats
:video BrandoVideo url, width, height, source, remoteId, thumbnailUrl
:file BrandoFile url, filename, filesize, contentType
:gallery BrandoGallery galleryObjects[BrandoGalleryObject] with sequence, image, video

The url field on images, videos, and files is auto-resolved to a full URL using Brando's media URL configuration.

Image sizes and srcset

The sizes field on images returns each size with a resolved URL and width descriptor, making it easy to build responsive <img srcset="..."> on the frontend:

{
  "sizes": {
    "small":  { "url": "/media/images/photo-small.jpg",  "width": 700 },
    "medium": { "url": "/media/images/photo-medium.jpg", "width": 1100 },
    "large":  { "url": "/media/images/photo-large.jpg",  "width": 1700 },
    "xlarge": { "url": "/media/images/photo-xlarge.jpg",  "width": 2100 }
  }
}

Width values are read from Brando's default_srcset config at runtime. Frontend usage:

function ResponsiveImage({ image }) {
  const srcSet = Object.values(image.sizes)
    .filter(s => s.width)
    .map(s => `${s.url} ${s.width}w`)
    .join(', ')

  return <img src={image.url} srcSet={srcSet} sizes="100vw" alt={image.alt} />
}

Relations

Relations (:belongs_to, :has_many, :has_one) are automatically exposed if the related module is included in the schemas list of your schema module. Otherwise they are skipped.

# Both Project and Client types will include the relationship field
use BrandoGraphql.Schema,
  schemas: [
    MyApp.Projects.Project,  # has belongs_to :client
    MyApp.Projects.Client
  ]

Hidden fields

These fields are always hidden by default:

  • creator_id, deleted_at, marked_as_deleted, password

Add more via hide_fields in the DSL:

graphql do
  hide_fields [:internal_notes, :admin_data]
end

Auto-preloading

Asset associations (image, video, file, gallery) are automatically preloaded when queried. You don't need to configure preloads manually.

Gallery assets additionally preload nested gallery_objects with their image and video associations.

Authentication

For public content (status: published), no authentication is needed — the same data is already public on the rendered site.

For elevated access, use the included bearer token plug:

# config/runtime.exs
config :brando_graphql,
  bearer_tokens: [System.get_env("API_BEARER_TOKEN")]
# router.ex
pipeline :api_auth do
  plug BrandoGraphql.Plug.Auth
end

scope "/api" do
  pipe_through [:api, :api_auth]
  forward "/graphql", Absinthe.Plug, schema: MyAppWeb.API.Schema
end

Media URL configuration

Image and file URLs are resolved automatically. The resolution strategy (in order):

  1. Explicit config: config :brando_graphql, media_url_prefix: "https://cdn.example.com/media/"
  2. Brando's Brando.Utils.media_url() (auto-detected at runtime)
  3. Fallback: "/media/"

For production with a CDN:

# config/runtime.exs
config :brando_graphql,
  media_url_prefix: System.get_env("CDN_URL", "https://cdn.example.com/media/")

Architecture

Blueprint (graphql do ... end)
    ↓  Spark DSL Extension
Transformer (persists config at compile time)
    ↓
Schema macro (reads DSL, generates Absinthe AST)
    ↓
TypeBuilder (attributes + assets + relations → object types)
    ↓
Resolver (GraphQL args → Brando.Query args → context function)
    ↓
Preloads (auto-preload visible assets)

The package follows the same architecture as brando_json_api — a Spark DSL extension that reads blueprint metadata at compile time and generates the API layer.

No changes needed to Brando core. The extensions: mechanism in Brando.Blueprint forwards extensions to Spark, which handles the DSL registration.

License

MIT

About

GraphQL API layer for Brando CMS blueprints via Absinthe

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages