npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

buro26-strapi-graphql

v0.1.0

Published

Extension and utilities for Strapi GraphQL

Readme

Strapi GraphQL Extension

Buro26’s Strapi GraphQL Extension supercharges your Strapi GraphQL API with:

  • Field-level authorization and middleware
  • Co-location of schema and resolvers
  • Fluent, type-safe builder APIs
  • Powerful testing, validation, and tracing utilities

Supports Strapi v4 and v5


🚀 Getting Started

npm i buro26-strapi-graphql

🏗️ Recommended: The Field Builder Pattern

The recommended way to build GraphQL fields is with createGraphQLFieldBuilder().
This fluent, type-safe builder gives you the best DX, composability, and testability.

Example: A Custom Query Field

import { createGraphQLFieldBuilder } from 'buro26-strapi-graphql';
import { nonNull, stringArg } from 'nexus';

export default createGraphQLFieldBuilder('Query')
  .fieldName('helloWorld')
  .args({ name: nonNull(stringArg()) })
  .outputType(nonNull('String'))
  .description('Returns a personalized greeting')
  .resolver(async (parent, args) => `Hello World ${args.name}`)
  .resolverConfig({ auth: false });

Why use the Field Builder?

  • Chainable, immutable API for safe, readable code
  • Type-safe context and args for resolvers, middleware, and auth
  • Built-in support for:
    • Middleware and authorization (local and global)
    • Argument validation (Zod)
    • Tracing/profiling
    • Deprecation and description
    • Field hooks (before/after resolve)
    • Testing in isolation
    • Batch/group registration
    • Composable presets (.use())

Composable Presets

Create reusable sets of middleware/auth logic and apply them to any field:

const adminPreset = createGraphQLFieldPreset(builder =>
  builder
    .middleware(requireAdmin)
    .authorize(adminAuth)
);

export default createGraphQLFieldBuilder('Query')
  .use(adminPreset)
  .fieldName('adminHello')
  .outputType('String')
  .resolver(() => 'Hello, admin!');

🔒 Field-Level Authorization and Middleware

Every field defined with createGraphQLFieldBuilder() or via Nexus’s t.field can use the extensions property to attach authorization and middleware logic.

Field-Level Authorization

  • Use the authorize field in extensions to add per-field authorization logic.
  • The function should return true (allow), false (deny), or throw an error.

Example:

t.field('myField', {
  type: 'String',
  extensions: {
    authorize: async (parent, args, ctx, info) => {
      // Only allow admins
      return ctx.state.user?.role === 'admin';
    }
  },
  resolve: () => 'Secret data'
});

Note:
If you use the builder, .authorize() will automatically set this property for you.


Field-Level Middleware

  • Use the middlewares field in extensions to add one or more middleware functions to a field.
  • Middleware functions wrap the resolver and can perform logic before/after, modify args, or short-circuit the resolver.

Example:

t.field('myField', {
  type: 'String',
  extensions: {
    middlewares: [
      next => async (root, args, ctx, info) => {
        console.log('Before');
        const result = await next(root, args, ctx, info);
        console.log('After');
        return result;
      }
    ]
  },
  resolve: () => 'With middleware'
});

Note:
If you use the builder, .middleware() will automatically set this property for you.


🌍 Global Middleware and Authorization

You can register global middleware and global authorization that apply to multiple fields based on type, field name, or tags.

Important:

Global middleware and authorization must be registered before any fields that consume them are built or registered.
This ensures that all relevant fields will pick up the global logic.

Example:

import { registerGlobalMiddleware, registerGlobalAuthorize } from 'buro26-strapi-graphql';

// Register global middleware for all admin-tagged Query fields
registerGlobalMiddleware(
  ({ type, tags }) => type === 'Query' && tags.includes('admin'),
  next => async (root, args, ctx, info) => {
    // ...admin logic...
    return next(root, args, ctx, info);
  }
);

// Register global authorization for a specific field
registerGlobalAuthorize(
  ({ fieldName }) => fieldName === 'deleteUser',
  async (parent, args, ctx) => ctx.state.user?.isAdmin
);

// Now define fields that will use these global middlewares/authorizations
export default createGraphQLFieldBuilder('Query')
  .fieldName('deleteUser')
  .outputType('Boolean')
  .tag('admin')
  .resolver(() => true);

Best Practice:

  • Always register global middleware and authorization at the top of your entry file (before any field or extension exports).
  • This guarantees that all fields will be able to consume the global logic.

📝 Best Practices

  • Use the builder’s .authorize() and .middleware() methods for most use-cases—they automatically set the correct extensions properties.
  • Use the extensions property directly for advanced or manual Nexus usage.
  • Register global middleware and authorization before any fields that should use them.
  • Use tags to target groups of fields for global logic.

Validation, Tracing, and Testing

Argument validation with Zod:

import { z } from 'zod';

const nameSchema = z.object({ name: z.string().min(1) });

export default createGraphQLFieldBuilder('Query')
  .fieldName('hello')
  .args({ name: 'String!' })
  .outputType('String')
  .validateArgs(nameSchema)
  .resolver((parent, args) => `Hello, ${args.name}!`);

Tracing/profiling:

export default createGraphQLFieldBuilder('Query')
  .fieldName('profiledHello')
  .outputType('String')
  .trace() // Pretty console log by default
  .resolver(async () => {
    await new Promise(r => setTimeout(r, 100));
    return 'Hello, profiled!';
  });

Testing in isolation:

const field = createGraphQLFieldBuilder('Query')
  .fieldName('hello')
  .outputType('String')
  .resolver((parent, args, ctx) => `Hello, ${args.name}!`);

const result = await field.test({
  args: { name: 'Henri' },
  context: { user: { id: '123' } }
});
console.log(result); // "Hello, Henri!"

Batch Registration and Field Groups

Register multiple fields at once:

import { createGraphQLFieldBuilder, registerFields } from 'buro26-strapi-graphql';

const fieldA = createGraphQLFieldBuilder('Query')
  .fieldName('foo')
  .outputType('String')
  .resolver(() => 'Foo');

const fieldB = createGraphQLFieldBuilder('Query')
  .fieldName('bar')
  .outputType('String')
  .resolver(() => 'Bar');

export default registerFields(fieldA, fieldB);

Apply shared logic to a group:

import { createFieldGroup } from 'buro26-strapi-graphql';

const requireUser = builder =>
  builder.middleware(next => async (root, args, ctx, info) => {
    if (!ctx.user) throw new Error('Not authenticated');
    return next(root, args, ctx, info);
  });

export default createFieldGroup([fieldA, fieldB], requireUser);

🛠️ Advanced: The Extension Builder

For advanced use-cases, you can use createGraphQLExtension() to build a full extension with custom types, resolvers, plugins, and more.

import { createGraphQLExtension } from 'buro26-strapi-graphql';
import { extendType } from 'nexus';

export default createGraphQLExtension()
  .typeDefs(`
    type HelloWorldResponse {
      greeting: String!
    }
  `)
  .types([
    extendType({
      type: 'Query',
      definition(t) {
        t.field('helloWorld', {
          type: 'HelloWorldResponse',
          resolve: () => ({ greeting: 'Hello, world!' })
        });
      }
    })
  ])
  .resolvers({
    Query: {
      helloWorld: () => ({ greeting: 'Hello, world!' })
    }
  });

Note:
For most use-cases, prefer the field builder.
Use the extension builder for advanced scenarios (custom plugins, full type overrides, etc).


🧩 Overriding and Extending Fields

You can override or extend fields in existing types using the overrideField helper:

import { createGraphQLExtension, overrideField } from 'buro26-strapi-graphql';
import { extendType } from 'nexus';

export default createGraphQLExtension(strapi => ({
  types() {
    return [
      extendType({
        type: 'MyType',
        definition(t) {
          overrideField<MyContentType>(t, {
            contentTypeName: 'api::my-type.my-type',
            fieldName: 'myField',
            authorize: async (root, args, context) => {
              // Authorization logic
              return true;
            },
            resolve: async (root) => {
              // Resolver is optional
              return false;
            }
          });
        }
      })
    ];
  }
}));

🧰 Utility Functions

This package provides a set of utility functions to make working with Strapi GraphQL resolvers easier and more typesafe:

Entity and Collection Resolvers

  • resolveEntity
    Resolve a single entity by ID.

    import { resolveEntity } from 'buro26-strapi-graphql';
    
    // Inside a resolver:
    return resolveEntity<MyContentType>('api::my-content-type.my-content-type', {
      args: { id: 'the id here' },
      parent,
      context,
      info,
    });
  • resolveEntityRelation
    Resolve a related entity from a relation field.

    import { resolveEntityRelation } from 'buro26-strapi-graphql';
    
    return resolveEntityRelation<MyContentType>(
      'api::my-content-type.my-content-type',
      'relationFieldName',
      { args, parent, context, info }
    );
  • resolveEntityCollection
    Resolve a collection of entities.

    import { createEntityCollectionResolver } from 'buro26-strapi-graphql';
    
    const resolver = createEntityCollectionResolver<MyContentType>('api::my-content-type.my-content-type');
    return resolver({ args, parent, context, info });
  • resolveEntityRelationCollection
    Resolve a collection of related entities from a relation field.

    import { resolveEntityRelationCollection } from 'buro26-strapi-graphql';
    
    return resolveEntityRelationCollection<MyContentType>(
      'api::my-content-type.my-content-type',
      'relationFieldName',
      { args, parent, context, info }
    );

Response Builders

  • toEntityResponse
    Typesafe builder for a single entity response.

    import { toEntityResponse } from 'buro26-strapi-graphql';
    
    return toEntityResponse(entity, 'api::my-content-type.my-content-type');
  • toEntityCollectionResponse
    Typesafe builder for a collection response.

    import { toEntityCollectionResponse } from 'buro26-strapi-graphql';
    
    return toEntityCollectionResponse(entities, 'api::my-content-type.my-content-type');

Argument and CRUD Utilities

  • resolveArgs
    Typesafe utility to transform GraphQL args for use with Strapi’s entity manager.

    import { resolveArgs } from 'buro26-strapi-graphql';
    
    const entityManagerArgs = resolveArgs(graphqlArgs);
  • shadowCRUD
    Typesafe utility to create shadow CRUD operations for a content type.

    import { shadowCRUD } from 'buro26-strapi-graphql';
    
    shadowCRUD('api::article.article')
      .disableActions(['create', 'update', 'delete']);
    
    shadowCRUD('api::category.category')
      .field('title')
      .disable();

🧪 Testing

To run tests:

bun test

🤝 Contributing

Pull requests and contributions are welcome!
See the repo for details.


👥 Authors and Acknowledgment

Buro26https://buro26.digital

Special thanks to all contributors and the open-source community for their support and contributions.


📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


🚦 Project Status

Active development.
Check issues for updates and planned features.


Questions or suggestions?
Open an issue or reach out to Buro26.