DEV Community

Vilius
Vilius

Posted on

A Typed Repository Layer for SharePoint CRUD in SPFx

A Typed Repository Layer for SharePoint CRUD in SPFx

PnPjs is a good low-level client for SharePoint. The problem is what application code starts to look like when every feature repeats list names, internal field names, lookup expansion, ETag handling, response mapping, and error translation.

I built spfx-data-kit as a small repository layer above @pnp/sp v4.

The contract

A feature works with a domain-oriented resource:

const result = await requests.update(
  id,
  { title: "Updated", status: "approved" },
  { etag },
);
Enter fullscreen mode Exit fullscreen mode

The feature does not need to know that title maps to SharePoint's Title field, or that an update may return no response body.

The public contract currently supports:

  • list
  • get
  • create
  • update
  • remove

Single-item results carry the current ETag:

{
  data: request,
  etag: '"7"'
}
Enter fullscreen mode Exit fullscreen mode

Lists keep entities ergonomic and expose ETags separately:

{
  data: requests,
  etags: {
    "42": '"7"'
  }
}
Enter fullscreen mode Exit fullscreen mode

What it adds above PnPjs

spfx-data-kit is not a replacement for PnPjs and does not attempt to cover all SharePoint APIs. It adds application-level conventions:

  • explicit domain-to-SharePoint field mapping;
  • typed create and update inputs;
  • stable error kinds for validation, not-found, conflict, permission, transient, and unknown failures;
  • first-class optimistic-concurrency inputs through ETags;
  • update re-reads after SharePoint's possible 204 No Content response;
  • bounded async paging with pageSize, maxPages, and an overall top cap;
  • a local in-memory adapter implementing the same CRUD contract;
  • cloning at local adapter boundaries to prevent accidental shared-state mutation.

This keeps TanStack Query in its proper place: TanStack can manage cache, refetch, invalidation, and optimistic UI while the repository owns SharePoint semantics.

React feature
    ↓
TanStack Query
    ↓
spfx-data-kit
    ↓
PnPjs v4
    ↓
SharePoint
Enter fullscreen mode Exit fullscreen mode

Why a local adapter?

A mocked PnPjs fluent chain tests whether a particular call sequence happened. It does not provide much confidence that the feature's CRUD behavior is coherent.

The local adapter lets contract tests exercise validation, ETag conflicts, cloning, IDs, and CRUD behavior without a tenant. Tenant validation remains a separate responsibility: permissions, required fields, content types, and tenant configuration cannot be proven locally.

Current status

This is an early MVP, released as source on GitHub rather than presented as a finished enterprise framework.

Verified locally:

  • 9 deterministic tests passing;
  • TypeScript build passing;
  • no-emission type check passing;
  • package dry-run passing;
  • npm dependency audit reporting zero vulnerabilities;
  • pre-publish secret scrub passing.

Not yet validated against a live SharePoint tenant. That is the next honest test: initialize it after super.onInit(), connect it to a real list, and verify permissions, field requirements, live CRUD, and stale-ETag conflicts.

The repository is open for review:

github.com/vystartasv/spfx-data-kit

Feedback on the contract matters more than adding another layer of abstractions. If this is only a shorter spelling of PnPjs, it is not useful enough. The goal is a small, explicit boundary that makes SharePoint application code easier to test and harder to get subtly wrong.

Top comments (0)