Naystack: Next.js Full-Stack Toolkit
Naystack is a full-stack utility library for Next.js applications, currently at version 1.7.26. It provides pre-built solutions for common web development challenges, including robust email-based authentication (with optional Google/Instagram OAuth), GraphQL API scaffolding, and file upload functionalities. The library is designed to be modular, allowing developers to integrate specific features as needed, and follows a 'bring-your-own database' philosophy, demonstrating integration with ORMs like Drizzle ORM in its examples. Naystack is actively maintained and focuses on providing a cohesive, opinionated yet flexible framework for server-side API routes and client-side React components within the Next.js ecosystem. Its key differentiator is providing integrated, end-to-end solutions for authentication, GraphQL, and file management without dictating the database layer.
Common errors
-
Error: SIGNING_KEY and REFRESH_KEY must be set in environment variables.
cause The required environment variables for JWT signing and refreshing are missing or undefined.fixDefine `SIGNING_KEY` and `REFRESH_KEY` in your `.env.local` file for local development, or in your deployment environment variables for production. Example: `SIGNING_KEY="super-secret-signing-key"`. -
Error: You must wrap your application with AuthWrapper to use auth hooks.
cause A client-side authentication hook (e.g., `useToken`, `useSignUp`) was called outside the React context provided by `AuthWrapper`.fixEnsure that the `AuthWrapper` component is placed high enough in your React component tree, typically in `app/layout.tsx`, to encompass all components that utilize Naystack's authentication hooks. -
Module not found: Can't resolve 'naystack/auth' in '...' OR Module not found: Can't resolve 'naystack/auth/client'
cause The `naystack` package is not installed, or the import path for a specific module is incorrect (e.g., missing `/client` subpath for client-side components).fixFirst, ensure `naystack` is installed (`pnpm add naystack`). Then, verify the import paths. Client-side modules typically require `/client` or `/graphql/client` subpaths, e.g., `import { AuthWrapper } from 'naystack/auth/client';`. -
TypeError: Cannot read properties of undefined (reading 'select') at setupEmailAuth
cause The `db` object or `UserTable` schema passed to `setupEmailAuth` is not correctly configured or is undefined, likely due to a database setup issue.fixVerify that your `db` connection and `UserTable` (or equivalent) schema are correctly initialized and imported into your `route.ts` file, and that the `getUser` and `createUser` functions access them properly.
Warnings
- gotcha Authentication functionality heavily relies on two critical environment variables: `SIGNING_KEY` and `REFRESH_KEY`. Failure to set these will lead to runtime errors and authentication failures.
- gotcha Client-side authentication hooks like `useToken()`, `useSignUp()`, and `useLogin()` require your application's root component (e.g., `app/layout.tsx`) to be wrapped with `AuthWrapper` to provide the necessary React context. Using these hooks outside of the `AuthWrapper` will result in runtime errors.
- gotcha Naystack is built for the Next.js App Router. Its server-side API handlers (`setupEmailAuth`) are designed to be exported directly from `route.ts` files within the `app/api` directory structure. Using it with the Pages Router or an incorrect App Router structure may not work as expected.
- gotcha Naystack lists `next`, `react`, and `react-dom` as peer dependencies. Ensure your project's installed versions of these packages are compatible with Naystack's specified ranges (`next: >=13`, `react: ^18 || ^19`, `react-dom: ^18 || ^19`). Incompatible versions can lead to unexpected behavior or build issues.
Install
-
npm install naystack -
yarn add naystack -
pnpm add naystack
Imports
- setupEmailAuth
const { setupEmailAuth } = require('naystack/auth');import { setupEmailAuth } from 'naystack/auth'; - AuthWrapper
import { AuthWrapper } from 'naystack/auth';import { AuthWrapper } from 'naystack/auth/client'; - useToken
import { useToken } from 'naystack/auth';import { useToken } from 'naystack/auth/client'; - useSignUp
import { useSignUp } from 'naystack/auth/client'; - ApolloWrapper
import { ApolloWrapper } from 'naystack/graphql/client';
Quickstart
import { setupEmailAuth } from 'naystack/auth';
import { AuthWrapper, useToken } from 'naystack/auth/client';
import { ApolloWrapper } from 'naystack/graphql/client';
import React from 'react';
// --- Simulate a Drizzle ORM setup for the server-side ---
// In a real app, this would be your database connection and schema
const db = {
select: () => ({ from: () => ({ where: () => ([{ id: 'user-id-123', password: 'hashed-password' }]) }) }),
insert: () => ({ values: () => ({ returning: () => ([{ id: 'new-user-id', password: 'new-hashed-password' }]) }) })
};
const UserTable = { id: 'id', email: 'email', password: 'password' };
const eq = (a: any, b: any) => ({}); // Dummy eq function
// --- Server-side (app/api/(auth)/email/route.ts) ---
// Ensure process.env.SIGNING_KEY and process.env.REFRESH_KEY are set
export const { GET, POST, PUT, DELETE } = setupEmailAuth({
getUser: async ({ email }) => {
// Replace with your actual database query
const [user] = await db.select({ id: UserTable.id, password: UserTable.password }).from(UserTable).where(eq(UserTable.email, email));
return user;
},
createUser: async (data) => {
// Replace with your actual database insertion
const [user] = await db.insert(UserTable).values(data).returning({ id: UserTable.id, password: UserTable.password });
return user;
},
onSignUp: async (userId, body) => {
console.log(`User ${userId} signed up.`);
}
});
// --- Client-side (app/layout.tsx) ---
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AuthWrapper>
<ApolloWrapper>
{children}
</ApolloWrapper>
</AuthWrapper>
</body>
</html>
);
}
// --- Client-side (app/page.tsx or any client component) ---
'use client';
function DashboardButton() {
const token = useToken();
return (
<div>
{token ? (
<button>Go to Dashboard (Logged In)</button>
) : (
<button>Sign Up / Login (Logged Out)</button>
)}
<p>Current Token: {token ? 'Present' : 'Not Present'}</p>
</div>
);
}
// To make the client component render within the quickstart context
export { DashboardButton };