Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Json Api Laravel Package

neomerx/json-api

Framework-agnostic PHP library implementing JSON:API v1.1. Builds compliant documents, relationships, compound includes, meta and errors. Parses/validates Accept/Content-Type and query params (pagination, sorting, sparse fields) to return proper 415/406 responses.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup for Laravel

  1. Install the package:

    composer require neomerx/json-api
    
  2. Define a resource schema (e.g., app/Schemas/PostSchema.php):

    use Neomerx\JsonApi\Encoder\Encoder;
    use Neomerx\JsonApi\Encoder\SchemaInterface;
    use Neomerx\JsonApi\Encoder\ContextInterface;
    
    class PostSchema implements SchemaInterface
    {
        public function getType(): string { return 'posts'; }
        public function getId($post): ?string { return $post->id; }
        public function getAttributes($post, ContextInterface $context): array
        {
            return [
                'title' => $post->title,
                'body'  => $post->body,
            ];
        }
        public function getRelationships($post, ContextInterface $context): array
        {
            return [
                'author' => [
                    SchemaInterface::RELATIONSHIP_DATA => $post->author,
                ],
            ];
        }
    }
    
  3. Encode a resource (e.g., in a controller):

    use Neomerx\JsonApi\Encoder\Encoder;
    
    public function show(Post $post)
    {
        $encoder = Encoder::instance([
            Post::class => PostSchema::class,
        ]);
    
        return response()->json(
            $encoder->encodeData($post),
            200,
            ['Content-Type' => 'application/vnd.api+json']
        );
    }
    
  4. Handle requests (e.g., validate Accept headers):

    use Neomerx\JsonApi\Parser\Parser;
    use Neomerx\JsonApi\Parser\ParserInterface;
    
    public function index(Request $request)
    {
        $parser = Parser::instance();
        $parser->parseAcceptHeader($request->header('Accept'));
    
        if (!$parser->isAccepted('application/vnd.api+json')) {
            return response('Unsupported media type', 415);
        }
    
        // Proceed with JSON API logic...
    }
    

First Use Case: CRUD Endpoint

For a posts resource with author relationships:

// GET /posts/1?include=author
$encoder = Encoder::instance([
    Post::class => PostSchema::class,
    Author::class => AuthorSchema::class,
])
->withIncludedPaths(['author']); // Pre-configure includes

$post = Post::find(1);
return response()->json($encoder->encodeData($post));

Implementation Patterns

1. Schema-Driven Development

  • Pattern: Group schemas by domain (e.g., app/Schemas/Api/Posts, app/Schemas/Api/Users).

  • Workflow:

    1. Define SchemaInterface for each model.
    2. Register schemas in a service provider (e.g., JsonApiServiceProvider):
      public function register()
      {
          $this->app->singleton(Encoder::class, function ($app) {
              return Encoder::instance([
                  Post::class => PostSchema::class,
                  Author::class => AuthorSchema::class,
              ]);
          });
      }
      
    3. Inject Encoder into controllers/services via Laravel’s DI.
  • Tip: Use trait-based schemas for shared logic:

    trait HasAuthorRelationship
    {
        public function getRelationships($model, ContextInterface $context): array
        {
            return [
                'author' => [
                    SchemaInterface::RELATIONSHIP_DATA => $model->author,
                ],
            ];
        }
    }
    

2. Request Parsing Workflow

  • Pattern: Parse and validate incoming requests before processing.

  • Steps:

    1. Validate Accept/Content-Type headers:
      $parser = Parser::instance();
      $parser->parseAcceptHeader($request->header('Accept'));
      $parser->parseContentTypeHeader($request->header('Content-Type'));
      
    2. Check for JSON API compliance:
      if (!$parser->isAccepted('application/vnd.api+json')) {
          return response('Unsupported media type', 415);
      }
      
    3. Parse query parameters (e.g., pagination, sorting, includes):
      $queryParams = $parser->parseQueryParams($request->query());
      $encoder->withIncludedPaths($queryParams->getIncludes());
      
  • Integration with Laravel: Use middleware to centralize parsing:

    class ValidateJsonApiRequest
    {
        public function handle($request, Closure $next)
        {
            $parser = Parser::instance();
            $parser->parseAcceptHeader($request->header('Accept'));
    
            if (!$parser->isAccepted('application/vnd.api+json')) {
                return response('Unsupported media type', 415);
            }
    
            return $next($request);
        }
    }
    

3. Handling Relationships

  • Pattern: Use lazy-loading or eager-loading with ContextInterface.

  • Example: Dynamically load relationships based on include paths:

    class PostSchema implements SchemaInterface
    {
        public function getRelationships($post, ContextInterface $context): array
        {
            $includes = $context->getIncludedPaths();
            $relationships = [];
    
            if (in_array('author', $includes)) {
                $relationships['author'] = [
                    SchemaInterface::RELATIONSHIP_DATA => $post->author,
                ];
            }
    
            if (in_array('comments', $includes)) {
                $relationships['comments'] = [
                    SchemaInterface::RELATIONSHIP_DATA => $post->comments->load('user'),
                ];
            }
    
            return $relationships;
        }
    }
    
  • Circular References: The package handles them natively. No manual checks needed.


4. Error Handling

  • Pattern: Convert Laravel exceptions to JSON API errors.

  • Example:

    try {
        // Business logic
    } catch (\Exception $e) {
        $error = Error::jsonApiError(
            'invalid_data',
            'The provided data is invalid',
            ['source' => ['pointer' => '/data/attributes/title']],
            422
        );
        return response()->json($error->toArray(), 422);
    }
    
  • Global Exception Handler:

    public function render($request, \Throwable $exception)
    {
        if ($exception instanceof \Illuminate\Validation\ValidationException) {
            $errors = $exception->errors();
            $errorCollection = new ErrorCollection();
            foreach ($errors as $field => $messages) {
                foreach ($messages as $message) {
                    $errorCollection->add(
                        Error::jsonApiError(
                            'validation_error',
                            $message,
                            ['source' => ['pointer' => "/data/attributes/{$field}"]]
                        )
                    );
                }
            }
            return response()->json($errorCollection->toArray(), 422);
        }
    
        return parent::render($request, $exception);
    }
    

5. Pagination and Filtering

  • Pattern: Use BaseQueryParser to extract and apply query params.

  • Example:

    $queryParams = $parser->parseQueryParams($request->query());
    $page = $queryParams->getPage();
    $perPage = $queryParams->getPerPage();
    $sort = $queryParams->getSort();
    $filter = $queryParams->getFilter();
    
    $posts = Post::query();
    if ($sort) {
        $posts->orderBy($sort['field'], $sort['direction'] ?? 'asc');
    }
    if ($filter) {
        $posts->where($filter['field'], $filter['operator'], $filter['value']);
    }
    
    $posts = $posts->paginate($perPage, ['*'], 'page', $page);
    
  • Integration with Laravel Pagination:

    $encoder->withMeta([
        'pagination' => [
            'total' => $posts->total(),
            'pages' => $posts->lastPage(),
            'current_page' => $posts->currentPage(),
        ],
    ]);
    

Gotchas and Tips

1. Schema Context Pitfalls

  • Gotcha: Forgetting to pass the ContextInterface to getAttributes()/getRelationships().
    • Fix: Always implement the full method signature:
      public function getAttributes($model, ContextInterface $context): array
      
    • Tip: Use the context to optimize queries:
      public function getRelationships($post, ContextInterface $context): array
      {
          $includes = $context->getIncludedPaths();
          if (in_array('comments', $includes)) {
              return [
                  'comments' => [
                      SchemaInterface::RELATIONSHIP_DATA => $post->comments()->with('user')->get(),
                  ],
              ];
          }
          return [];
      }
      

2. URL Prefixes and Absolute Links

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor
spatie/laravel-javascript-views