Skip to content
Back to skills

Strawberry Graphql

ASecurity

Strawberry GraphQL library for Python with FastAPI integration, type-safe resolvers, DataLoader patterns, and subscriptions. Use when building GraphQL APIs with Python, implementing real-time features, or creating federated schemas.

  • 57 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 17, 2026
developmentpythonfastapiapidatabasebackend

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill strawberry-graphql --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Strawberry Graphql?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Strawberry Graphql
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-strawberry-graphql/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-strawberry-graphql)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: strawberry-graphql
description: Strawberry GraphQL library for Python with FastAPI integration, type-safe resolvers, DataLoader patterns, and subscriptions. Use when building GraphQL APIs with Python, implementing real-time features, or creating federated schemas.
context: fork
agent: backend-system-architect
version: 1.0.0
tags: [graphql, strawberry, fastapi, dataloader, subscriptions, federation, python, 2026]
author: OrchestKit
user-invocable: false
---

# Strawberry GraphQL Patterns

Type-safe GraphQL in Python with code-first schema definition.

## Overview

- Complex data relationships (nested queries, multiple entities)
- Client-driven data fetching (mobile apps, SPAs)
- Real-time features (subscriptions for live updates)
- Federated microservice architecture

## When NOT to Use

- Simple CRUD APIs (REST is simpler)
- Internal microservice communication (use gRPC)

## Schema Definition

```python
import strawberry
from datetime import datetime
from strawberry import Private

@strawberry.enum
class UserStatus:
    ACTIVE = "active"
    INACTIVE = "inactive"

@strawberry.type
class User:
    id: strawberry.ID
    email: str
    name: str
    status: UserStatus
    password_hash: Private[str]  # Not exposed in schema

    @strawberry.field
    def display_name(self) -> str:
        return f"{self.name} ({self.email})"

    @strawberry.field
    async def posts(self, info: strawberry.Info, limit: int = 10) -> list["Post"]:
        return await info.context.post_loader.load_by_user(self.id, limit)

@strawberry.type
class Post:
    id: strawberry.ID
    title: str
    content: str
    author_id: strawberry.ID

    @strawberry.field
    async def author(self, info: strawberry.Info) -> User:
        return await info.context.user_loader.load(self.author_id)

@strawberry.input
class CreateUserInput:
    email: str
    name: str
    password: str
```

## Query and Mutation

```python
@strawberry.type
class Query:
    @strawberry.field
    async def user(self, info: strawberry.Info, id: strawberry.ID) -> User | None:
        return await info.context.user_service.get(id)

    @strawberry.field
    async def me(self, info: strawberry.Info) -> User | None:
        user_id = info.context.current_user_id
        return await info.context.user_service.get(user_id) if user_id else None

@strawberry.type
class Mutation:
    @strawberry.mutation
    async def create_user(self, info: strawberry.Info, input: CreateUserInput) -> User:
        return await info.context.user_service.create(
            email=input.email, name=input.name, password=input.password
        )

    @strawberry.mutation
    async def delete_user(self, info: strawberry.Info, id: strawberry.ID) -> bool:
        await info.context.user_service.delete(id)
        return True
```

## DataLoader (N+1 Prevention)

```python
from strawberry.dataloader import DataLoader

class UserLoader(DataLoader[str, User]):
    def __init__(self, user_repo):
        super().__init__(load_fn=self.batch_load)
        self.user_repo = user_repo

    async def batch_load(self, keys: list[str]) -> list[User]:
        users = await self.user_repo.get_many(keys)
        user_map = {u.id: u for u in users}
        return [user_map.get(key) for key in keys]

class GraphQLContext:
    def __init__(self, request, user_service, user_repo, post_repo):
        self.request = request
        self.user_service = user_service
        self.user_loader = UserLoader(user_repo)
        self._current_user_id = None

    @property
    def current_user_id(self) -> str | None:
        if self._current_user_id is None:
            token = self.request.headers.get("authorization", "").replace("Bearer ", "")
            self._current_user_id = decode_token(token) if token else None
        return self._current_user_id
```

## FastAPI Integration

```python
from fastapi import FastAPI, Request, Depends
from strawberry.fastapi import GraphQLRouter

schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)

async def get_context(request: Request, user_service=Depends(get_user_service)) -> GraphQLContext:
    return GraphQLContext(request=request, user_service=user_service, ...)

graphql_router = GraphQLRouter(schema, context_getter=get_context, graphiql=True)

app = FastAPI()
app.include_router(graphql_router, prefix="/graphql")
```

## Subscriptions

```python
from typing import AsyncGenerator

@strawberry.type
class Subscription:
    @strawberry.subscription
    async def user_updated(self, info: strawberry.Info, user_id: strawberry.ID) -> AsyncGenerator[User, None]:
        async for message in info.context.pubsub.subscribe(f"user:{user_id}:updated"):
            yield User(**message)

    @strawberry.subscription
    async def notifications(self, info: strawberry.Info) -> AsyncGenerator["Notification", None]:
        user_id = info.context.current_user_id
        if not user_id:
            raise PermissionError("Authentication required")
        async for message in info.context.pubsub.subscribe(f"user:{user_id}:notifications"):
            yield Notification(**message)
```

## Authentication and Authorization

```python
from strawberry.permission import BasePermission

class IsAuthenticated(BasePermission):
    message = "User is not authenticated"

    async def has_permission(self, source, info: strawberry.Info, **kwargs) -> bool:
        return info.context.current_user_id is not None

class IsAdmin(BasePermission):
    message = "Admin access required"

    async def has_permission(self, source, info: strawberry.Info, **kwargs) -> bool:
        user_id = info.context.current_user_id
        if not user_id:
            return False
        user = await info.context.user_service.get(user_id)
        return user and user.role == "admin"

# Usage
@strawberry.type
class Query:
    @strawberry.field(permission_classes=[IsAuthenticated])
    async def me(self, info: strawberry.Info) -> User:
        return await info.context.user_service.get(info.context.current_user_id)

    @strawberry.field(permission_classes=[IsAdmin])
    async def all_users(self, info: strawberry.Info) -> list[User]:
        return await info.context.user_service.list_all()
```

## Error Handling with Union Types

```python
@strawberry.type
class CreateUserSuccess:
    user: User

@strawberry.type
class UserError:
    message: str
    code: str
    field: str | None = None

@strawberry.type
class CreateUserError:
    errors: list[UserError]

CreateUserResult = strawberry.union("CreateUserResult", [CreateUserSuccess, CreateUserError])

@strawberry.type
class Mutation:
    @strawberry.mutation
    async def create_user(self, info: strawberry.Info, input: CreateUserInput) -> CreateUserResult:
        errors = []
        if not is_valid_email(input.email):
            errors.append(UserError(message="Invalid email", code="INVALID_EMAIL", field="email"))
        if errors:
            return CreateUserError(errors=errors)

        try:
            user = await info.context.user_service.create(**input.__dict__)
            return CreateUserSuccess(user=user)
        except DuplicateEmailError:
            return CreateUserError(errors=[UserError(message="Email exists", code="DUPLICATE_EMAIL", field="email")])
```

## Key Decisions

| Decision | Recommendation |
|----------|----------------|
| Schema approach | Code-first with Strawberry types |
| N+1 prevention | DataLoader for all nested resolvers |
| Pagination | Relay-style cursor pagination |
| Auth | Permission classes, context-based |
| Errors | Union types for mutations |
| Subscriptions | Redis PubSub for horizontal scaling |

## Anti-Patterns (FORBIDDEN)

```python
# NEVER make database calls in resolver loops (N+1 queries!)
for post_id in self.post_ids:
    posts.append(await db.get_post(post_id))

# CORRECT: Use DataLoader
return await info.context.post_loader.load_many(self.post_ids)

# NEVER expose internal IDs without encoding
id: int  # Exposes auto-increment ID!

# CORRECT: Use opaque IDs
id: strawberry.ID  # base64 encoded

# NEVER skip input validation in mutations
```

## Related Skills

- `api-design-framework` - REST API patterns
- `grpc-python` - gRPC alternative
- `streaming-api-patterns` - WebSocket patterns

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.