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

nexios-core

v1.0.1

Published

Axios-compatible HTTP client built on fetch — with first-class Next.js App Router support, a plugin ecosystem, and an experimental Chain API.

Readme

nexios-core

A fetch-based HTTP client with a full Axios-style API
First-class support for Next.js App Router · 10 plugins · Experimental Chain API

npm version npm downloads TypeScript Node.js License: MIT Zero Dependencies


✨ Why nexios-core?

| | nexios-core | axios | native fetch | |---|---|---|---| | Familiar API | ✅ Axios-identical | ✅ | ❌ | | Built on fetch | ✅ | ❌ XHR/http | ✅ | | Next.js ISR / Tags | ✅ built-in | ❌ | manual | | Interceptors | ✅ | ✅ | ❌ | | Auto-retry | ✅ built-in | ❌ | ❌ | | Token Refresh | ✅ plugin | ❌ | ❌ | | Circuit Breaker | ✅ plugin | ❌ | ❌ | | Rate Limiter | ✅ plugin | ❌ | ❌ | | Mock Adapter | ✅ plugin | ✅ | ❌ | | GraphQL helper | ✅ plugin | ❌ | ❌ | | SSE streaming | ✅ plugin | ❌ | manual | | Pagination | ✅ plugin | ❌ | ❌ | | Zod validation | ✅ plugin | ❌ | ❌ | | Chain API | ✅ beta | ❌ | ❌ | | TypeScript | ✅ full | ⚠️ partial | ⚠️ | | Zero dependencies | ✅ | ❌ | ✅ |


📦 Installation

# npm
npm install nexios-core

# pnpm
pnpm add nexios-core

# yarn
yarn add nexios-core

# bun
bun add nexios-core

Requirements: Node.js ≥ 18, or any environment with native fetch support (Deno, Bun, Browser, Edge Runtime)


🚀 Quick Start

import nexios from 'nexios-core'

// GET
const { data } = await nexios.get('/api/users')

// POST
const { data: user } = await nexios.post('/api/users', {
  name:  'Ahmed',
  email: '[email protected]',
})

// TypeScript — fully typed
interface User { id: number; name: string; email: string }
const { data } = await nexios.get('/api/users')
//     ^? User[]

📖 API 1 — Axios Style (Stable ✓)

HTTP Methods

nexios.get(url, config?)
nexios.post(url, data?, config?)
nexios.put(url, data?, config?)
nexios.patch(url, data?, config?)
nexios.delete(url, config?)
nexios.head(url, config?)
nexios.options(url, config?)

// Form Data — automatically sets Content-Type: multipart/form-data
nexios.postForm(url, data?, config?)
nexios.putForm(url, data?, config?)
nexios.patchForm(url, data?, config?)

// URL builder — without making a request
nexios.getUri({ url: '/users', params: { page: 2 } })
// → "https://api.example.com/users?page=2"

Request Config

const config: NexiosConfig = {
  // ── Core ─────────────────────────────────────────
  url:     '/api/users',
  method:  'GET',
  baseURL: 'https://api.example.com',
  headers: { 'X-Custom': 'value' },
  params:  { page: 1, tags: ['a', 'b'] },   // → ?page=1&tags=a&tags=b
  data:    { name: 'Ahmed' },               // auto JSON.stringify

  // ── Params serializer (Axios-compatible) ──────────
  paramsSerializer: {
    indexes: null,   // → tags=a&tags=b      (default)
    indexes: false,  // → tags[]=a&tags[]=b
    indexes: true,   // → tags[0]=a&tags[1]=b
    serialize: (params) => customSerialize(params),
  },

  // ── Timeout & Cancel ──────────────────────────────
  timeout: 10_000,                  // 10 seconds
  signal:  controller.signal,       // AbortController
  cancelToken: token,               // Axios-compatible

  // ── Auth ──────────────────────────────────────────
  auth: { username: 'user', password: 'pass' }, // → Basic Auth header
  withCredentials: true,            // send cookies cross-origin

  // ── Response ──────────────────────────────────────
  responseType: 'json',             // json | text | blob | arrayBuffer | formData | stream
  validateStatus: (s) => s < 500,  // custom — default: 200–299

  // ── Next.js ───────────────────────────────────────
  cache: 'no-store',                // fetch cache directive
  next: {
    revalidate: 60,                 // ISR
    tags: ['posts', 'featured'],    // on-demand revalidation
  },

  // ── Retry ─────────────────────────────────────────
  retry: {
    attempts:           3,
    delay:              500,                           // fixed ms
    delay:              (attempt) => attempt * 1000,  // dynamic
    retryOn:            [429, 500, 502, 503, 504],
    retryOnNetworkError: true,
  },

  // ── Transforms ────────────────────────────────────
  transformRequest:  [(data, headers) => data],
  transformResponse: [(data) => data],

  // ── Size limits ───────────────────────────────────
  maxContentLength: 5_000_000,      // 5MB response limit
  maxBodyLength:    2_000_000,      // 2MB request body limit

  // ── XSRF ──────────────────────────────────────────
  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',
}

Custom Instance

import { Nexios } from 'nexios-core'

// lib/api.ts
const api = new Nexios({
  baseURL:  'https://api.example.com/v1',
  timeout:  10_000,
  headers:  { 'X-App-Version': '1.0.0' },
  retry: { attempts: 3, delay: 500 },
})

// Add auth token to every request
api.interceptors.request.use((config) => {
  const token = getToken()
  if (token) config.headers['Authorization'] = `Bearer ${token}`
  return config
})

// Centralized error handling
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.status === 401) router.push('/login')
    return Promise.reject(error)
  }
)

export default api

Hierarchical Defaults (Axios-identical)

import nexios from 'nexios-core'

// Applied to all requests
nexios.defaults.headers.common['Authorization'] = `Bearer ${token}`
nexios.defaults.headers.common['X-App-ID']      = 'my-app'

// Applied to POST only
nexios.defaults.headers.post['Content-Type'] = 'application/json'

// Applied to GET only
nexios.defaults.headers.get['Accept'] = 'application/json'

// Global baseURL & timeout
nexios.defaults.baseURL  = 'https://api.example.com'
nexios.defaults.timeout  = 10_000

Error Handling

import { isNexiosError, NexiosError, ErrorCodes } from 'nexios-core'

try {
  const { data } = await nexios.get('/api/data')
} catch (err) {
  if (!isNexiosError(err)) throw err   // re-throw non-nexios errors

  // Properties
  err.status         // 404 | 500 | undefined
  err.code           // "ERR_BAD_REQUEST" | "ERR_NETWORK" | ...
  err.response?.data // server error body
  err.config.url     // failed URL
  err.isTimeout      // true if request timed out
  err.isCancelled    // true if request was cancelled
  err.isNetworkError // true if no connection

  // Error codes
  switch (err.code) {
    case ErrorCodes.ERR_BAD_REQUEST:   /* 400–499 */ break
    case ErrorCodes.ERR_BAD_RESPONSE:  /* 500–599 */ break
    case ErrorCodes.ERR_NETWORK:       /* offline  */ break
    case ErrorCodes.ECONNABORTED:      /* timeout  */ break
    case ErrorCodes.ERR_CANCELED:      /* canceled */ break
  }

  // JSON-safe — for logging
  logger.error(err.toJSON())
}

Timeout & Cancellation

import { CancelToken } from 'nexios-core'

// Timeout
await nexios.get('/api/slow', { timeout: 5_000 })

// CancelToken — Axios-compatible
const { token, cancel } = CancelToken.source()
nexios.get('/api/data', { cancelToken: token })
setTimeout(() => cancel('User left'), 2000)

// AbortController — Web standard
const controller = new AbortController()
nexios.get('/api/data', { signal: controller.signal })
controller.abort()

// React: cancel on unmount
useEffect(() => {
  const { token, cancel } = CancelToken.source()
  api.get('/users', { cancelToken: token }).then(r => setUsers(r.data))
  return () => cancel()
}, [])

// throwIfRequested — for polling loops
token.throwIfRequested()  // throws if already cancelled

Parallel Requests

const [users, posts] = await nexios.all([
  nexios.get('/api/users'),
  nexios.get('/api/posts'),
])

// with spread
nexios
  .all([nexios.get('/api/a'), nexios.get('/api/b')])
  .then(nexios.spread((a, b) => {
    console.log(a.data, b.data)
  }))

⚡ Next.js App Router

import { withRevalidate, withTags, withNext, noCache, forceCache, isrConfig } from 'nexios-core'

// ISR — revalidate every 60 seconds
const { data: posts } = await nexios.get('/api/posts', withRevalidate(60))

// On-demand revalidation with tags
const { data } = await nexios.get('/api/posts', withTags(['posts', 'featured']))

// No cache (force-dynamic)
const { data } = await nexios.get('/api/live', noCache())

// Always from cache (force-static)
const { data } = await nexios.get('/api/config', forceCache())

// ISR + tags combined
const { data } = await nexios.get('/api/products', isrConfig(3600, ['products']))

// Manual — full control
const { data } = await nexios.get('/api/dashboard', {
  next:    { revalidate: 30, tags: ['dashboard'] },
  headers: { Authorization: `Bearer ${token}` },
})

// On-demand revalidation — Route Handler
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
export async function POST(req: Request) {
  const { tag } = await req.json()
  revalidateTag(tag)
  return Response.json({ revalidated: true })
}

🔌 Plugins

1. Token Auto-Refresh

import { applyTokenRefresh } from 'nexios-core/plugins'

applyTokenRefresh(api, {
  // Called when a 401 is received
  refreshToken: async () => {
    const res = await authApi.post('/auth/refresh')
    return res.data.accessToken
  },
  // Optional: customize how the token is applied
  applyToken: (config, token) => ({
    ...config,
    headers: { ...config.headers, Authorization: `Bearer ${token}` },
  }),
  onRefreshFailure: () => router.push('/login'),
})
// Now every 401 → auto refresh → retry

2. Request Deduplication

import { applyDedupe } from 'nexios-core/plugins'

applyDedupe(api)

// 10 components requesting the same endpoint simultaneously
// → only one real request is sent; all callers share the same response
await Promise.all([
  api.get('/users'),
  api.get('/users'),
  api.get('/users'),
])
// fetchCount === 1 ✓

3. In-Memory Cache (TTL + LRU)

import { applyCache } from 'nexios-core/plugins'

const store = applyCache(api, {
  ttl:     60_000,  // 1 minute
  maxSize: 500,     // max entries (LRU eviction)
})

await api.get('/users')  // → real fetch
await api.get('/users')  // → from cache
await api.get('/users')  // → from cache

store.clear()            // clear all cache entries
store.prune()            // remove expired entries only
store.size()             // current entry count

4. Circuit Breaker

import { applyCircuitBreaker } from 'nexios-core/plugins'

const { breaker } = applyCircuitBreaker(api, {
  failureThreshold: 5,         // open circuit after 5 failures
  successThreshold: 2,         // close after 2 successes in HALF_OPEN
  resetTimeout:     30_000,    // retry after 30 seconds
  onStateChange: (from, to) => {
    logger.warn(`Circuit: ${from} → ${to}`)
  },
})

breaker.getState()    // "CLOSED" | "OPEN" | "HALF_OPEN"
breaker.getFailures() // consecutive failure count
breaker.reset()       // manual reset

5. Rate Limiter (Token Bucket)

import { applyRateLimit } from 'nexios-core/plugins'

const { limiter } = applyRateLimit(api, {
  max:        10,       // 10 requests
  per:        1_000,    // per second
  onExceeded: 'queue',  // 'queue' | 'throw'
  maxQueue:   50,       // max pending requests in queue
})

limiter.getTokens()      // current token balance
limiter.getQueueLength() // pending requests in queue

6. Pagination

import { paginate, paginateAll, offsetStrategy, cursorStrategy } from 'nexios-core/plugins'

// Fetch all pages at once
const allPosts = await paginateAll(api, '/posts', {
  getNextConfig: offsetStrategy({ pageSize: 20 }),
})

// Async generator — page by page with full control
for await (const page of paginate(api, '/posts', {
  getNextConfig: offsetStrategy({ pageSize: 20 }),
})) {
  renderPage(page.items)       // page.items | page.page | page.response
  if (page.page === 5) break   // stop whenever you want
}

// Cursor-based
const allFeed = await paginateAll(api, '/feed', {
  getItems:      (res) => res.data.items,
  getNextConfig: cursorStrategy({
    cursorPath:  'meta.nextCursor',  // path to cursor in response
    cursorParam: 'cursor',           // query param name
  }),
})

7. Mock Adapter

import { createMockAdapter } from 'nexios-core/plugins'

const mock = createMockAdapter(api)

// Static response
mock.onGet('/users').reply(200, [{ id: 1, name: 'Ahmed' }])

// Dynamic — handler function
mock.onPost('/users').reply((config) => ({
  status: 201,
  data:   { id: 99, ...config.data },
}))

// Regex pattern
mock.onGet(/\/users\/\d+/).reply(200, { id: 1 })

// Simulate network error
mock.onGet('/broken').reply({ networkError: true })

// Simulate delay
mock.onGet('/slow').reply({ status: 200, data: {}, delay: 2000 })

mock.reset()    // clear all registered routes
mock.restore()  // restore real fetch

8. GraphQL Client

import { createGQLClient } from 'nexios-core/plugins'

const gql = createGQLClient(api, {
  endpoint:     '/graphql',
  throwOnError: true,  // throws GraphQLError if errors are present
})

// Query
const { data } = await gql.query(`
  query GetUser($id: ID!) {
    user(id: $id) { id name email }
  }
`, { id: '1' })

// Mutation
const { data } = await gql.mutate(`
  mutation CreatePost($input: PostInput!) {
    createPost(input: $input) { id title }
  }
`, { input: { title: 'Hello' } })

9. Server-Sent Events (SSE)

import { subscribeSSE } from 'nexios-core/plugins'

const sub = subscribeSSE(api, '/api/events', {
  onMessage(event) {
    console.log(event.type, event.data)
  },
  onClose()  { console.log('stream closed') },
  onError(e) { console.error(e) },

  // Auto-reconnect
  reconnect:    3000,   // reconnect after 3 seconds
  maxReconnects: 5,
})

// Close the subscription
sub.close()
console.log(sub.open) // false

10. Zod Schema Validation

import { z } from 'zod'
import { validatedGet, applyZodValidation } from 'nexios-core/plugins'

const UserSchema = z.object({
  id:    z.number(),
  name:  z.string(),
  email: z.string().email(),
})

// Standalone
const { data } = await validatedGet(api, '/users/1', UserSchema)
//     ^? { id: number; name: string; email: string }

// Extension on the instance
const validatedApi = applyZodValidation(api)
const { data } = await validatedApi.getValidated('/users/1', UserSchema)

🔗 API 2 — Chain (@beta)

⚠️ Experimental — may change without notice. Logs a warning to the console on first use. Not recommended for production yet.

import nexios, { createAPI } from 'nexios-core'

const api = createAPI(nexios, { baseURL: 'https://api.example.com' })

// ── URL building ─────────────────────────────────────────
api.users.url()           // "/users"
api.users[1].posts.url()  // "/users/1/posts"

// ── GET / POST / PUT / PATCH / DELETE ────────────────────
await api.users.get()
await api.users.get({ page: 2, limit: 20 })
await api.users.post({ name: 'Ahmed' })
await api.users[1].put({ name: 'Updated' })
await api.users[1].patch({ active: false })
await api.users[1].delete()

// ── Modifier chain (each modifier returns a new chain — immutable) ─
const { data: posts } = await api.users[1].posts
  .cache(60)                            // ISR revalidate
  .tag('posts', 'user-1')              // cache tags
  .cacheStrategy('force-cache')        // fetch cache directive
  .timeout(5_000)                      // 5 seconds
  .retry(3)                            // 3 attempts
  .retry({ attempts: 3, delay: 500 }) // or full config object
  .headers({ Authorization: `Bearer ${token}` })
  .params({ sort: 'desc' })
  .get({ page: 2 })                    // extra params at execute time

// ── Schema validation (Zod) ───────────────────────────────
import { z } from 'zod'
const PostSchema = z.object({ id: z.number(), title: z.string() })

const { data: post } = await api.posts[1].schema(PostSchema).get()
//     ^? { id: number; title: string }

// ── Reuse partial chains ──────────────────────────────────
const authed = api.users.headers({ Authorization: `Bearer ${token}` })
await authed.get()                // GET /users
await authed.post({ name: 'x' }) // POST /users
await authed[1].delete()          // DELETE /users/1

// ── Debug ─────────────────────────────────────────────────
api.users[1].posts.inspect()
// { url: "/users/1/posts", resolvedURL: "https://api.example.com/users/1/posts", timeout: undefined, ... }

License

MIT © 2025 nexios-core contributors