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

auto-server-client

v0.3.0

Published

A unified, secure data-access layer for Next.js App Router that eliminates the need to write individual Server Actions for every API request

Readme

auto-server-client

A unified, secure data-access layer for Next.js App Router. One client that works in Server Components, Client Components, and Route Handlers — with no configuration required.

Features

  • Universal client — same API everywhere, context detected automatically
  • Secure by default — tokens never leave the server
  • End-to-end type safety — schema-first typed paths and responses
  • Auth guards — protect proxy routes without touching business logic
  • React hooksuseAutoQuery and useAutoMutation for client components

Installation

npm install auto-server-client

Setup

1. Set environment variable

API_URL=https://api.example.com

2. Create the proxy route

// app/api/proxy/route.ts
import { createProxyHandler } from "auto-server-client"

export const POST = createProxyHandler()

That's it. The handler reads API_URL and the accessToken cookie automatically.


Universal Client

createAutoClient() detects whether it's running on the server or in the browser and picks the right strategy automatically:

  • Server (Server Component, Route Handler) → calls the external API directly
  • Browser (Client Component) → routes through /api/proxy, keeping the token server-only
// works in a Server Component, Client Component, or Route Handler — identical code
const client = createAutoClient()

const posts = await client.get("/posts")
const post  = await client.post("/posts", { title: "Hello" })
await client.put("/posts/1", { title: "Updated" })
await client.delete("/posts/1")

When called inside a Client Component the request is triggered from the browser and routed through /api/proxy. The token is never exposed — the proxy reads it server-side before forwarding.


End-to-End Type Safety

Pass a schema type to createAutoClient() to get typed paths, typed responses, and typed request bodies.

// lib/api.ts
import { createAutoClient } from "auto-server-client"

type MyApi = {
  GET: {
    "/posts":     { response: Post[] }
    "/users/me":  { response: User }
  }
  POST: {
    "/posts": { body: CreatePostInput; response: Post }
  }
  DELETE: {
    "/posts": { response: void }
  }
}

export const client = createAutoClient<MyApi>()
const posts = await client.get("/posts")         // Post[]  — inferred, no cast needed
const user  = await client.get("/users/me")      // User    — inferred

await client.post("/posts", { title: "Hello" })  // body must be CreatePostInput

await client.get("/wrong")                       // compile error — not in schema
await client.post("/posts", { nme: "Hi" })       // compile error — wrong body shape

Without a schema, the client behaves as before — any path, manual type cast:

const client = createAutoClient()
const data = await client.get<Post[]>("/posts")

Auth Guards

Guards run inside createProxyHandler() before any request is forwarded to the external API.

Protect all routes

import { createProxyHandler, requireAuth } from "auto-server-client"

export const POST = createProxyHandler({
  guard: requireAuth(), // returns 401 if accessToken cookie is missing
})

Mixed public and protected routes

import { createProxyHandler, requireAuth, allow } from "auto-server-client"

export const POST = createProxyHandler({
  guards: {
    "GET /posts":    allow(),        // public
    "POST /posts":   requireAuth(),  // must be logged in
    "DELETE /posts": requireAuth(),  // must be logged in
  },
  default: allow(), // paths not listed are public
})

Flip default to protect everything unless explicitly allowed:

export const POST = createProxyHandler({
  guards: {
    "GET /posts": allow(), // the only public route
  },
  default: requireAuth(), // everything else requires auth
})

Custom guard

import { defineGuard } from "auto-server-client"
import { cookies } from "next/headers"

const myGuard = defineGuard(async () => {
  const token = (await cookies()).get("accessToken")?.value
  if (!token) return { deny: 401 }

  const payload = verifyJwt(token)
  if (!payload) return { deny: 401 }

  return { allow: true }
})

export const POST = createProxyHandler({ guard: myGuard })

Role-based access control (admin, editor, etc.) should stay in your backend. Guards at the proxy layer only answer one question: is there a valid session?


React Hooks

For Client Components that need reactive state (loading, error, re-fetching).

useAutoQuery

"use client"

import { useAutoQuery } from "auto-server-client"

export default function PostList() {
  const { data, loading, error } = useAutoQuery<Post[]>("/posts")

  if (loading) return <p>Loading...</p>
  if (error)   return <p>Error: {error}</p>

  return <ul>{data?.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

useAutoMutation

"use client"

import { useAutoMutation } from "auto-server-client"

export default function CreatePost() {
  const { mutate, loading } = useAutoMutation<Post>("/posts", "POST")

  return (
    <button
      disabled={loading}
      onClick={() => mutate({ title: "New Post" })}
    >
      Create
    </button>
  )
}

API Reference

createAutoClient<Schema>()

Returns a universal client. Detects server vs. browser at runtime.

| Method | Signature | |---|---| | get | (path) => Promise<Response> | | post | (path, body) => Promise<Response> | | put | (path, body) => Promise<Response> | | delete | (path) => Promise<Response> |

createProxyHandler(options?)

Creates the Next.js POST route handler for /api/proxy.

| Option | Type | Description | |---|---|---| | guard | Guard | Runs on every request | | guards | Record<"METHOD /path", Guard> | Per-path guards | | default | Guard | Fallback when no per-path guard matches. Defaults to allow() |

requireAuth()

Returns 401 if the accessToken cookie is missing.

allow()

Always passes. Use to explicitly mark a route as public in a guards map.

serverQuery<T>(path)

One-line server-side fetch for Server Components. Reads API_URL and accessToken cookie automatically.

const user = await serverQuery<User>("/users/me")

createServerClient(config)

Low-level server-side client for custom token handling.

const client = createServerClient({
  baseURL: process.env.API_URL!,
  getToken: async () => (await cookies()).get("myToken")?.value,
})

How It Works

Client Component                  Server
      │                              │
      │  POST /api/proxy             │
      │  { method, path, body }      │
      │─────────────────────────────>│  guard runs (is there a token?)
      │                              │  token read from cookie
      │                              │  request forwarded to external API
      │  { data }                    │
      │<─────────────────────────────│

Server Components skip the proxy entirely and call the external API in-process.

License

MIT