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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@mngl/sanctum-spa-client

v0.4.0

Published

Lightweight TypeScript client for Laravel Sanctum SPA XSRF protection using Axios.

Readme

@mngl/sanctum-spa-client

npm version License: MIT

A lightweight TypeScript client for Laravel Sanctum SPA authentication with CSRF protection, designed to work seamlessly with React Router and modern frontend frameworks.

🚀 Features

  • 🔐 Laravel Sanctum Integration - Full SPA authentication support
  • 🛡️ CSRF Protection - Automatic XSRF token handling
  • 🎯 TypeScript First - Complete type safety and IntelliSense support
  • React Query Ready - Perfect integration with TanStack Query
  • 🔄 Client/Server APIs - Separate implementations for frontend and SSR
  • 📦 Lightweight - Minimal dependencies (only Axios as peer dependency)

📦 Installation

npm install @mngl/sanctum-spa-client

🛠️ Setup

Project Structure

app/
├── src/
│   └── services/
│       └── api/
│           ├── backend/
│           │   └── backendApi.server.ts
│           ├── frontend/
│           │   └── clientApi.client.ts
│           └── queries/
│               └── loginUser.ts

Backend API Configuration (Server-side)

backendApi.server.ts

import * as process from "node:process";
import { BackendApi } from "@mngl/sanctum-spa-client";
import axios from "axios";

const axiosClient = axios.create({
	baseURL: process.env.API_BASE_URL,
	withCredentials: true,
	withXSRFToken: true,
	headers: {
		"Content-Type": "application/json",
		// Ensure this Origin matches your Sanctum configuration
		Origin: process.env.FRONTEND_URL,
	},
});

export const backendApi = new BackendApi(axiosClient);

Client API Configuration (Frontend)

clientApi.client.ts

import { ClientApi } from "@mngl/sanctum-spa-client";
import axios from "axios";

const clientApiClient = axios.create({
	baseURL: "/api", // This should match your proxy configuration
	withCredentials: true,
	withXSRFToken: true,
	headers: {
		"Content-Type": "application/json",
	},
});

export const clientApi = new ClientApi(clientApiClient);

⚠️ Important: Proxy Configuration

When using the client API with baseURL, you must configure your development server to proxy API requests to your backend. Here's an example Vite configuration: /api vite.config.ts

export default defineConfig({
	server: {
		proxy: {
			"/api": {
				target: process.env.API_BASE_URL, // e.g., "http://localhost:8000"
				rewrite: (path) => path.replace(/^\/api/, ""),
				changeOrigin: true,
			},
		},
	},
});

📚 Usage

CSRF Protection Setup

Add CSRF protection at your root route to ensure all routes have access to the XSRF token: root.tsx

import { data, type LoaderFunctionArgs } from "react-router";
import { CSRFProtection } from "@mngl/sanctum-spa-client";
import { backendApi } from "~/services/api/backend/backendApi.server";

export async function loader({ request }: LoaderFunctionArgs) {
	return data(null, {
		headers: await new CSRFProtection(backendApi).ensureToken(request),
	});
}

Creating API Query Configurations

Define your API queries with proper typing: queries/loginUser.ts

import type { ApiRequestConfig } from "@mngl/sanctum-spa-client";
import type { User } from "~/types/resources/user";

export type LoginData = {
	email: string;
	password: string;
};

export type LoginSuccess = User;

export const loginUser = (data: LoginData): ApiRequestConfig<LoginData> => ({
	url: "auth/login",
	method: "POST",
	data,
});

Making API Requests

Direct Usage

import { clientApi } from "~/services/api/frontend/clientApi.client";
import { loginUser } from "~/services/api/queries/loginUser";

// Using request method (returns full response object)
const response = await clientApi.request(loginUser({ 
	email: "[email protected]", 
	password: "password" 
}));

if (response.success) {
	console.log("User logged in:", response.data);
} else {
	console.error("Login failed:", response.errorData);
}

// Using requestAndUnwrap (throws on error, returns data directly)
try {
	const user = await clientApi.requestAndUnwrap(loginUser({ 
		email: "[email protected]", 
		password: "password" 
	}));
	console.log("User logged in:", user);
} catch (error) {
	console.error("Login failed:", error);
}

Integration with React Query

For optimal developer experience, integrate with TanStack Query: hooks/useLoginMutation.ts

import { useMutation } from "@tanstack/react-query";
import { clientApi } from "~/services/api/frontend/clientApi.client";
import {
	type LoginData,
	type LoginSuccess,
	loginUser,
} from "~/services/api/queries/loginUser";

export const useLoginMutation = () =>
	useMutation<LoginSuccess, Error, LoginData>({
		mutationFn: (body) =>
			clientApi.requestAndUnwrap<LoginSuccess>(loginUser(body)),
	});

Usage in React component:

import { useLoginMutation } from "~/hooks/useLoginMutation";

function LoginForm() {
	const loginMutation = useLoginMutation();

	const handleSubmit = (formData: LoginData) => {
		loginMutation.mutate(formData, {
			onSuccess: (user) => {
				console.log("Welcome,", user.name);
			},
			onError: (error) => {
				console.error("Login failed:", error.message);
			},
		});
	};

	return (
		<form onSubmit={handleSubmit}>
			{/* Your form JSX */}
			<button 
				type="submit" 
				disabled={loginMutation.isPending}
			>
				{loginMutation.isPending ? "Logging in..." : "Login"}
			</button>
		</form>
	);
}

📖 API Reference

ClientApi

  • request<TData, TErrData>(config: ApiRequestConfig) - Makes a request and returns the full response object
  • requestAndUnwrap<TData>(config: ApiRequestConfig) - Makes a request and returns only the data (throws on error)

BackendApi

  • request<TData, TErrData>(request: Request, config: ApiRequestConfig) - Server-side request method that accepts Web API Request object

CSRFProtection

  • ensureToken(request: Request) - Ensures XSRF token is available and returns appropriate headers

🔧 TypeScript Support

This package is built with TypeScript and provides full type safety. All API responses are properly typed, and you can extend the types for your specific use cases.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

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

⚠️ Stability Notice

This package is currently experimental. While it's functional and tested, the API may change in future versions. Use with caution in production environments.