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

nexoflow-sdk

v0.3.8

Published

Official SDK for the NexoFlow Developer API - fetch AI-generated blog posts and content with full TypeScript support. Zero dependencies, built-in retries, works in any server runtime.

Readme

nexoflow-sdk

The official TypeScript SDK for the NexoFlow Developer API.

npm version License: MIT

What is NexoFlow?

NexoFlow is an AI-powered content automation platform. It lets you generate, schedule, and publish blog posts and social media content - all from one dashboard. NexoFlow handles AI writing, image generation, and multi-channel publishing (WordPress or any JavaScript framework via the Developer API), then delivers your content so you can display it on any website.

nexoflow-sdk gives you a clean, type-safe way to fetch that content from any JavaScript or TypeScript backend. Zero dependencies, full TypeScript support, built-in retries, and works everywhere Node.js runs.

Why use the SDK?

  • Zero dependencies - nothing to audit, nothing to break.
  • Full TypeScript support - every response is typed, autocomplete works out of the box.
  • Built-in retries & timeouts - transient failures are handled automatically with exponential backoff.
  • Async iterators - paginate through thousands of posts with a simple for await loop.
  • Framework-agnostic - works in Next.js, Nuxt, SvelteKit, Astro, Remix, Express, Fastify, Cloudflare Workers, Deno, Bun, and any server runtime.
  • ISR-ready - pass revalidate and the SDK sets the right cache headers for Next.js Incremental Static Regeneration.

Install

npm install nexoflow-sdk
# or with yarn / pnpm
yarn add nexoflow-sdk
pnpm add nexoflow-sdk

Quick Start

import { NexoFlow } from "nexoflow-sdk"

const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })

// List recent posts
const { data } = await nf.posts.list({ limit: 10 })
console.log(data.posts)       // PostListItem[]
console.log(data.pagination)  // { page, limit, total, hasMore }

// Get a single post by slug
const { data: post } = await nf.posts.get("my-first-post")
console.log(post.title)
console.log(post.content) // full HTML content

// Iterate over ALL posts (auto-paginates)
for await (const post of nf.posts.iter()) {
  console.log(post.title)
}

Related posts

List responses include relatedPosts and relatedPostSlugs (the same ordered slug array). When you fetch a single post with posts.get(slug), the API also returns relatedArticles: one card per slug with title, meta title, featured image URL, and published date. Unpublished or missing neighbors still appear in order with null fields.


Server-Side Usage Only

Your NexoFlow API key (pk_live_*) is a secret. Treat it like a database password.

Do NOT:

  • Import nexoflow-sdk in browser-side code or React client components
  • Hard-code the key in source files that ship to the browser
  • Use the key in inline browser scripts, Vite client bundles, or Angular services

Do:

  • Store the key in an environment variable (e.g. NEXOFLOW_API_KEY)
  • Only call the SDK from server-side code (Server Components, API routes, server loaders, etc.)

If the SDK detects it is running in a browser (window is defined), it prints a console warning. Execution is not blocked, but the warning indicates a security misconfiguration.


Configuration

const nf = new NexoFlow({
  // Required
  apiKey: process.env.NEXOFLOW_API_KEY!,

  // Optional
  baseUrl: "https://nexoflow.net",  // default
  timeout: 15_000,                   // 15 seconds (default)
  revalidate: 60,                    // Next.js ISR hint in seconds (default)

  // Custom fetch (useful for polyfills, edge runtimes, tests)
  fetch: customFetchFn,

  // Retry transient failures (5xx, network errors)
  retry: {
    attempts: 3, // default
    delay: 500,  // base delay in ms, doubles each retry (default)
  },

  // Debug logging (request/response details)
  debug: true,

  // Lifecycle hooks
  hooks: {
    beforeRequest: ({ url, init }) => {
      console.log("Requesting:", url)
    },
    afterResponse: (res) => {
      console.log("Status:", res.status)
    },
  },
})

| Option | Type | Default | Description | | ------------ | ------------------ | ---------------------- | ------------------------------------------------- | | apiKey | string | required | Your project API key (pk_live_*) | | baseUrl | string | https://nexoflow.net | NexoFlow instance URL | | revalidate | number \| false | 60 | Default ISR cache time (seconds) for Next.js | | timeout | number | 15000 | Request timeout (ms) | | fetch | typeof fetch | globalThis.fetch | Custom fetch implementation | | retry | object | { attempts: 3, delay: 500 } | Retry policy for transient failures | | debug | boolean | false | Log request/response details to console | | hooks | object | undefined | beforeRequest / afterResponse lifecycle hooks |


Response Format

Every SDK method returns a consistent envelope:

{
  data: T,               // the API response payload
  rateLimit?: {           // extracted from response headers (when available)
    limit?: number,
    remaining?: number,
    reset?: number,
  }
}
const { data, rateLimit } = await nf.posts.list({ limit: 5 })

console.log(data.posts)
console.log(data.pagination)
console.log(rateLimit?.remaining)

Posts

nf.posts.list(params?)    // List posts (paginated)
nf.posts.get(slug, opts?) // Get single post with full content
nf.posts.all(params?)     // Get ALL posts (auto-paginated)
nf.posts.slugs()          // Get all slugs (for static generation)
nf.posts.iter(params?)    // Async iterator over all posts

List params: page, limit, tag, category, sort ("newest" | "oldest")

Get options: format ("html" | "markdown"), styled (boolean)

SEO / robots meta

Both PostListItem (list responses) and Post (single-post response) include:

| Field | Type | Description | |-------|------|-------------| | noIndex | boolean | When true, tell search engines not to index this post | | noFollow | boolean | When true, tell search engines not to follow links on this post | | readingTimeMinutes | number | Estimated reading time (min 1). Render as "X min read". | | path | string | Resolved URL path built from the project's permalink structure (e.g. /2024/03/my-post). Use directly as an href. |

Use them when configuring robots meta tags or framework metadata:

// Next.js App Router — app/blog/[slug]/page.tsx
import type { Metadata } from "next"
import { NexoFlow } from "nexoflow-sdk"

const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>
}): Promise<Metadata> {
  const { slug } = await params
  const { data: post } = await nf.posts.get(slug)
  return {
    title: post.metaTitle || post.title,
    description: post.metaDescription ?? undefined,
    robots: {
      index: !post.noIndex,
      follow: !post.noFollow,
    },
  }
}

You can also check the flag in a list response to skip rendering a post or to add robots meta manually:

for (const post of data.posts) {
  if (post.noIndex) continue // skip noindex posts from sitemap, etc.
}

Site feature flags & permalink structure

Use nf.siteSettings.get() to read the project's global settings once at build time:

const { data: siteSettings } = await nf.siteSettings.get()

// permalinkStructure is informational — each post already exposes a resolved `path`
console.log(siteSettings.permalinkStructure) // e.g. "/%year%/%month%/%slug%"

const {
  enableReadingTime,   // show "X min read" labels?
  enableRelatedPosts,  // render related articles section?
  enableSocialSharing, // render share buttons?
  enableSchemaMarkup,  // inject JSON-LD BlogPosting schema?
} = siteSettings.features

Reading time

Every post includes readingTimeMinutes. Gate the render with the feature flag:

{post.siteFeatures.enableReadingTime && (
  <span>{post.readingTimeMinutes} min read</span>
)}

Related posts

The relatedArticles array on single-post responses already contains resolved cards. Use the toggle to decide whether to render the section:

{post.siteFeatures.enableRelatedPosts && post.relatedArticles.length > 0 && (
  <RelatedPostsSection articles={post.relatedArticles} />
)}

Social sharing

The enableSocialSharing flag tells you whether to render share buttons. Build them with the post's URL (using path) and title:

{post.siteFeatures.enableSocialSharing && (
  <ShareButtons
    url={`https://yoursite.com${post.path}`}
    title={post.title}
  />
)}

JSON-LD schema markup

When enableSchemaMarkup is true, inject a BlogPosting JSON-LD block in your page head (for example via your framework’s metadata or head API).

// Next.js App Router — build jsonLd then pass to your head/layout helper
if (post.siteFeatures.enableSchemaMarkup) {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: post.metaTitle || post.title,
    description: post.metaDescription ?? undefined,
    image: post.featuredImageUrl ?? undefined,
    datePublished: post.publishedAt ?? undefined,
    dateModified: post.updatedAt,
    author: post.authorData
      ? { "@type": "Person", name: post.authorData.name }
      : undefined,
  }
  // e.g. serialize with JSON.stringify(jsonLd) and emit as application/ld+json
}

Examples

Blog index with pagination:

const { data } = await nf.posts.list({ limit: 12, category: "engineering" })

for (const post of data.posts) {
  console.log(`${post.title} - ${post.excerpt}`)
}

if (data.pagination.hasMore) {
  const { data: page2 } = await nf.posts.list({ limit: 12, page: 2 })
}

Static site generation (get all slugs):

const slugs = await nf.posts.slugs()
// [{ slug: "intro-to-nextjs" }, { slug: "deploy-to-vercel" }, ...]

Async iterator - process every post without manual pagination:

for await (const post of nf.posts.iter({ category: "news" })) {
  console.log(post.title)
}

Things to Do

For location-based content pages with structured attraction data.

nf.thingsToDo.list(params?)    // List pages (paginated)
nf.thingsToDo.get(slug, opts?) // Get single page with attractions
nf.thingsToDo.all(params?)     // Get ALL pages (auto-paginated)
nf.thingsToDo.slugs()          // Get all slugs
nf.thingsToDo.iter(params?)    // Async iterator over all pages

List params: page, limit, city, state, sort

Get options: format ("json" | "html"). With "html", the response includes page.contentHtml plus top-level contentStyles and layoutHints for styling and layout guidance.

SEO: Each list item and full page includes noIndex (boolean). When it is true, tell crawlers not to index the URL-for example in Next.js App Router:

// app/things-to-do/[slug]/page.tsx
import type { Metadata } from "next"
import { NexoFlow } from "nexoflow-sdk"

const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>
}): Promise<Metadata> {
  const { slug } = await params
  const { data } = await nf.thingsToDo.get(slug)
  const page = data.page
  return {
    title: page.metaTitle || page.pageTitle,
    description: page.metaDescription ?? undefined,
    ...(page.noIndex ? { robots: { index: false, follow: false } } : {}),
  }
}

Maps / embeds

Each attraction includes:

  • mapEmbedSrc - HTTPS URL only. Use this for <iframe src={attraction.mapEmbedSrc} /> (React, Vue, Svelte, etc.). This avoids passing full HTML into src, which browsers treat as a URL string and breaks the embed.
  • mapEmbed - Full <iframe …></iframe> HTML from NexoFlow. Render with dangerouslySetInnerHTML (or your framework’s equivalent), not as an iframe src.
  • mapLink - Opens the location in Google Maps in a new tab.

mapEmbedSrc may be null if only a short link was stored and the API could not derive a synchronous embed URL; fall back to mapEmbed or mapLink in that case.


Error Handling

import { NexoFlow, NexoFlowError } from "nexoflow-sdk"

try {
  const { data } = await nf.posts.get("nonexistent")
} catch (err) {
  if (err instanceof NexoFlowError) {
    console.log(err.status)         // 404
    console.log(err.code)           // "HTTP_404"
    console.log(err.message)        // "Post not found."
    console.log(err.requestId)      // server request ID (if available)
    console.log(err.isNotFound)     // true
    console.log(err.isUnauthorized) // false
    console.log(err.isRateLimited)  // false
  }
}

Framework Examples

Next.js (App Router)

Use the SDK in Server Components, Route Handlers, or generateStaticParams. Never import it in client components.

// app/blog/page.tsx - Server Component
import { NexoFlow } from "nexoflow-sdk"

const nf = new NexoFlow({
  apiKey: process.env.NEXOFLOW_API_KEY!,
  revalidate: 60,
})

export default async function BlogPage() {
  const { data } = await nf.posts.list({ limit: 12 })

  return (
    <main>
      {data.posts.map((post) => (
        <article key={post.slug}>
          {post.featuredImageUrl && (
            <img src={post.featuredImageUrl} alt={post.title} />
          )}
          <a href={`/blog/${post.slug}`}><h2>{post.title}</h2></a>
          <p>{post.excerpt}</p>
        </article>
      ))}
      {data.pagination.hasMore && (
        <a href={`/blog?page=${data.pagination.page + 1}`}>Next page</a>
      )}
    </main>
  )
}
// app/blog/[slug]/page.tsx - Single post with static generation
import { NexoFlow, NexoFlowError } from "nexoflow-sdk"
import { notFound } from "next/navigation"

const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })

export async function generateStaticParams() {
  return await nf.posts.slugs()
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  try {
    const { data: post } = await nf.posts.get(params.slug, { styled: true })
    return (
      <article>
        <h1>{post.title}</h1>
        {post.contentStyles && (
          <style dangerouslySetInnerHTML={{ __html: post.contentStyles }} />
        )}
        <div dangerouslySetInnerHTML={{ __html: post.content }} />
      </article>
    )
  } catch (err) {
    if (err instanceof NexoFlowError && err.isNotFound) notFound()
    throw err
  }
}

Nuxt 3

Use in server routes - the API key stays on the server:

// server/api/posts.ts
import { NexoFlow } from "nexoflow-sdk"

const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })

export default defineEventHandler(async () => {
  const { data } = await nf.posts.list({ limit: 20 })
  return data
})

In pages/blog.vue, use a Nuxt script setup block to call useFetch("/api/posts"), then render:

<template>
  <article v-for="post in blogData.posts" :key="post.slug">
    <NuxtLink :to="`/blog/${post.slug}`">
      <h2>{{ post.title }}</h2>
    </NuxtLink>
    <p>{{ post.excerpt }}</p>
  </article>
</template>

SvelteKit

Use in load functions (+page.server.ts):

// src/routes/blog/+page.server.ts
import { NexoFlow } from "nexoflow-sdk"
import { NEXOFLOW_API_KEY } from "$env/static/private"

const nf = new NexoFlow({ apiKey: NEXOFLOW_API_KEY })

export async function load() {
  const { data } = await nf.posts.list({ limit: 20 })
  return { posts: data.posts, pagination: data.pagination }
}

Astro

Use in frontmatter (runs at build time or SSR):

---
// src/pages/blog.astro
import { NexoFlow } from "nexoflow-sdk"

const nf = new NexoFlow({ apiKey: import.meta.env.NEXOFLOW_API_KEY })
const { data } = await nf.posts.list({ limit: 20 })
---

<html>
<body>
  {data.posts.map((post) => (
    <article>
      <a href={`/blog/${post.slug}`}><h2>{post.title}</h2></a>
      <p>{post.excerpt}</p>
    </article>
  ))}
</body>
</html>

Remix

Use in loader functions:

// app/routes/blog.tsx
import { json } from "@remix-run/node"
import { useLoaderData, Link } from "@remix-run/react"
import { NexoFlow } from "nexoflow-sdk"

const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })

export async function loader() {
  const { data } = await nf.posts.list({ limit: 20 })
  return json(data)
}

export default function Blog() {
  const { posts } = useLoaderData<typeof loader>()
  return (
    <main>
      {posts.map((post) => (
        <article key={post.slug}>
          <Link to={`/blog/${post.slug}`}><h2>{post.title}</h2></Link>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </main>
  )
}

Express / Fastify

import express from "express"
import { NexoFlow } from "nexoflow-sdk"

const app = express()
const nf = new NexoFlow({ apiKey: process.env.NEXOFLOW_API_KEY! })

app.get("/api/posts", async (req, res) => {
  const { data } = await nf.posts.list({ limit: 20 })
  res.json(data)
})

app.listen(3000)

Edge Runtimes (Cloudflare Workers, Vercel Edge)

The SDK uses globalThis.fetch and AbortSignal.timeout - both available natively in edge runtimes:

export default {
  async fetch(request: Request, env: Env) {
    const nf = new NexoFlow({ apiKey: env.NEXOFLOW_API_KEY })
    const { data } = await nf.posts.list({ limit: 5 })
    return Response.json(data)
  },
}

TypeScript

Every response is fully typed. Import individual types as needed:

import type {
  Post,
  PostListItem,
  RelatedArticle,
  SiteFeatures,
  SiteSettingsResponse,
  Category,
  Author,
  Tag,
  Pagination,
  ApiResponse,
  RateLimitInfo,
} from "nexoflow-sdk"

Requirements

  • Node.js 18+ (uses native fetch and AbortSignal.timeout)
  • Works with Next.js, Nuxt, SvelteKit, Astro, Remix, Express, Fastify, Angular (server-side), Cloudflare Workers, Deno, and Bun

Links

License

MIT