Next.js Integration

Learn how to integrate Qryn with Next.js for full-stack applications.

Installation

Install Qryn and configure it with Next.js for seamless integration.

Install Dependencies

npm install qryn
npm install -D @types/node

Basic Setup

// lib/qryn.ts
import { Qryn } from 'qryn';

const qryn = new Qryn({
  database: {
    type: 'sqlite',
    connection: './database.sqlite'
  }
});

export default qryn;

API Routes

Create API routes with Qryn in Next.js.

API Route Setup

Create API routes with Qryn

Dynamic API Route

// pages/api/qryn/[...path].ts
import { NextApiRequest, NextApiResponse } from 'next';
import qryn from '../../../lib/qryn';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    const { path } = req.query;
    const pathString = Array.isArray(path) ? path.join('/') : path;
    
    const result = await qryn.handleRequest({
      method: req.method,
      url: `/api/qryn/${pathString}`,
      headers: req.headers,
      body: req.body
    });
    
    res.status(result.status).json(result.data);
  } catch (error) {
    console.error('API Error:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
}

App Router API Route

// app/api/qryn/[...path]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import qryn from '../../../../lib/qryn';

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const path = searchParams.get('path') || '';
    
    const result = await qryn.handleRequest({
      method: 'GET',
      url: `/api/qryn/${path}`,
      headers: Object.fromEntries(request.headers.entries()),
      body: null
    });
    
    return NextResponse.json(result.data, { status: result.status });
  } catch (error) {
    console.error('API Error:', error);
    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

export async function POST(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const path = searchParams.get('path') || '';
    const body = await request.json();
    
    const result = await qryn.handleRequest({
      method: 'POST',
      url: `/api/qryn/${path}`,
      headers: Object.fromEntries(request.headers.entries()),
      body
    });
    
    return NextResponse.json(result.data, { status: result.status });
  } catch (error) {
    console.error('API Error:', error);
    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

Server Components

Use Qryn in Next.js Server Components for server-side data fetching.

Server Component Example

// app/users/page.tsx
import qryn from '../../lib/qryn';

export default async function UsersPage() {
  const users = await qryn.query({
    model: 'User',
    where: { status: 'active' },
    limit: 10
  });

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.data.map((user) => (
          <li key={user.id}>
            <h3>{user.name}</h3>
            <p>{user.email}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

Server Actions

// app/actions/user.ts
'use server';

import qryn from '../../lib/qryn';
import { revalidatePath } from 'next/cache';

export async function createUser(formData: FormData) {
  try {
    const name = formData.get('name') as string;
    const email = formData.get('email') as string;
    
    const user = await qryn.create({
      model: 'User',
      data: { name, email }
    });
    
    revalidatePath('/users');
    return { success: true, user };
  } catch (error) {
    return { success: false, error: 'Failed to create user' };
  }
}

export async function updateUser(id: string, formData: FormData) {
  try {
    const name = formData.get('name') as string;
    const email = formData.get('email') as string;
    
    const user = await qryn.update({
      model: 'User',
      id,
      data: { name, email }
    });
    
    revalidatePath('/users');
    return { success: true, user };
  } catch (error) {
    return { success: false, error: 'Failed to update user' };
  }
}

Client Components

Use Qryn in client components with proper data fetching.

Client Component with SWR

'use client';

import useSWR from 'swr';
import { useState } from 'react';

const fetcher = (url: string) => fetch(url).then((res) => res.json());

export default function UsersList() {
  const { data, error, mutate } = useSWR('/api/qryn/User', fetcher);
  const [isCreating, setIsCreating] = useState(false);

  const createUser = async (userData: { name: string; email: string }) => {
    setIsCreating(true);
    try {
      const response = await fetch('/api/qryn/User', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(userData)
      });
      
      if (response.ok) {
        mutate(); // Revalidate data
      }
    } catch (error) {
      console.error('Failed to create user:', error);
    } finally {
      setIsCreating(false);
    }
  };

  if (error) return <div>Failed to load users</div>;
  if (!data) return <div>Loading...</div>;

  return (
    <div>
      <h2>Users</h2>
      <ul>
        {data.data.map((user: any) => (
          <li key={user.id}>
            <h3>{user.name}</h3>
            <p>{user.email}</p>
          </li>
        ))}
      </ul>
      
      <button 
        onClick={() => createUser({ name: 'New User', email: 'new@example.com' })}
        disabled={isCreating}
      >
        {isCreating ? 'Creating...' : 'Add User'}
      </button>
    </div>
  );
}

Client Component with React Query

'use client';

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';

export default function UsersList() {
  const queryClient = useQueryClient();
  const [isCreating, setIsCreating] = useState(false);

  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/qryn/User').then((res) => res.json())
  });

  const createUserMutation = useMutation({
    mutationFn: (userData: { name: string; email: string }) =>
      fetch('/api/qryn/User', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(userData)
      }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });

  const createUser = async (userData: { name: string; email: string }) => {
    setIsCreating(true);
    try {
      await createUserMutation.mutateAsync(userData);
    } catch (error) {
      console.error('Failed to create user:', error);
    } finally {
      setIsCreating(false);
    }
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Failed to load users</div>;

  return (
    <div>
      <h2>Users</h2>
      <ul>
        {users?.data.map((user: any) => (
          <li key={user.id}>
            <h3>{user.name}</h3>
            <p>{user.email}</p>
          </li>
        ))}
      </ul>
      
      <button 
        onClick={() => createUser({ name: 'New User', email: 'new@example.com' })}
        disabled={isCreating}
      >
        {isCreating ? 'Creating...' : 'Add User'}
      </button>
    </div>
  );
}

Middleware

Use Next.js middleware with Qryn for authentication and authorization.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Check if the request is for Qryn API
  if (request.nextUrl.pathname.startsWith('/api/qryn')) {
    // Check for authentication token
    const token = request.headers.get('authorization');
    
    if (!token) {
      return NextResponse.json(
        { error: 'Authentication required' },
        { status: 401 }
      );
    }
    
    // Validate token (implement your validation logic)
    if (!isValidToken(token)) {
      return NextResponse.json(
        { error: 'Invalid token' },
        { status: 403 }
      );
    }
    
    // Add user info to headers
    const user = getUserFromToken(token);
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-id', user.id);
    requestHeaders.set('x-user-role', user.role);
    
    return NextResponse.next({
      request: {
        headers: requestHeaders,
      },
    });
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: '/api/qryn/:path*',
};

function isValidToken(token: string): boolean {
  // Implement your token validation logic
  return true;
}

function getUserFromToken(token: string): { id: string; role: string } {
  // Implement your token parsing logic
  return { id: 'user-123', role: 'user' };
}

Configuration

Configure Qryn for Next.js with proper environment variables and settings.

Environment Variables

# .env.local
DATABASE_URL=postgresql://user:pass@localhost:5432/qryn
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-secret-key
NODE_ENV=development

Next.js Configuration

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverComponentsExternalPackages: ['qryn']
  },
  env: {
    DATABASE_URL: process.env.DATABASE_URL,
    REDIS_URL: process.env.REDIS_URL,
    JWT_SECRET: process.env.JWT_SECRET
  }
};

module.exports = nextConfig;

Qryn Configuration

// lib/qryn.ts
import { Qryn } from 'qryn';

const qryn = new Qryn({
  database: {
    type: 'postgresql',
    connection: process.env.DATABASE_URL
  },
  cache: {
    type: 'redis',
    connection: process.env.REDIS_URL
  },
  security: {
    auth: {
      enabled: true,
      secret: process.env.JWT_SECRET
    }
  }
});

export default qryn;

Best Practices

Do

  • • Use Server Components for data fetching
  • • Implement proper error handling
  • • Use environment variables
  • • Implement authentication middleware
  • • Use proper caching strategies
  • • Optimize bundle size
  • • Use TypeScript for type safety

Don't

  • • Skip error handling
  • • Hardcode configuration
  • • Ignore authentication
  • • Use inefficient queries
  • • Skip caching
  • • Ignore bundle optimization
  • • Skip TypeScript