Back to BlogEngineering

Building Scalable APIs with Next.js Server Actions

Engineering Team
Nov 20, 2024
8 min read

Building Scalable APIs with Next.js Server Actions


Next.js Server Actions have revolutionized how we build API endpoints, bringing the data layer closer to our React components while maintaining security and type safety. In this post, we'll explore how we migrated a production application from traditional REST endpoints to Server Actions and the benefits we gained.


The Old Way: REST API Routes


Previously, our application used traditional Next.js API routes. For a simple user profile update, we needed:


  • An API route handler (`/api/user/profile`)
  • Request validation middleware
  • Database query logic
  • Error handling and logging
  • Client-side fetch wrapper with error handling

  • This meant code spread across multiple files and a lot of boilerplate for even simple operations.


    Enter Server Actions


    Server Actions allow us to define server-side functions directly in our components or separate server modules. They run exclusively on the server, have automatic POST semantics, and integrate seamlessly with React's form handling.


    Benefits We Observed


    1. 40% Less Boilerplate Code


    Server Actions eliminate the need for manual API route creation, fetch calls, and serialization logic. What used to take 50+ lines of code now takes about 30.


    2. Built-in Type Safety


    With Server Actions, TypeScript types flow directly from server to client. No more maintaining separate API contract definitions or OpenAPI specs.


    3. Progressive Enhancement


    Server Actions work with standard HTML forms, meaning your app functions even before JavaScript loads. This is a game-changer for perceived performance and accessibility.


    4. Automatic Revalidation


    Combined with Next.js caching primitives like `revalidatePath` and `revalidateTag`, Server Actions make cache invalidation straightforward and explicit.


    Real-World Example


    Here's a side-by-side comparison of updating a user profile:


    Before (API Routes):

    
    

    // app/api/user/profile/route.ts

    export async function POST(request: Request) {

    const session = await getSession()

    if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })


    const data = await request.json()

    const validated = profileSchema.parse(data)


    await db.user.update({

    where: { id: session.userId },

    data: validated

    })


    return Response.json({ success: true })

    }


    // components/profile-form.tsx

    const onSubmit = async (data) => {

    const res = await fetch('/api/user/profile', {

    method: 'POST',

    headers: { 'Content-Type': 'application/json' },

    body: JSON.stringify(data)

    })


    if (!res.ok) throw new Error('Update failed')

    router.refresh()

    }


    After (Server Actions):

    
    

    // app/actions/profile.ts

    'use server'


    export async function updateProfile(data: ProfileData) {

    const session = await getSession()

    if (!session) throw new Error('Unauthorized')


    const validated = profileSchema.parse(data)


    await db.user.update({

    where: { id: session.userId },

    data: validated

    })


    revalidatePath('/profile')

    }


    // components/profile-form.tsx

    {/* form fields */}


    Performance Considerations


    Server Actions are optimized for frequent, small data operations. For large file uploads or long-running processes, traditional API routes may still be preferable. We use Server Actions for:


  • Form submissions
  • CRUD operations
  • Data mutations with cache revalidation
  • User interactions that modify server state

  • Migration Strategy


    We migrated incrementally:


    1. **Start with new features** - Build new features with Server Actions

    2. **Migrate simple endpoints** - Convert straightforward CRUD operations

    3. **Tackle complex flows** - Refactor more complex API routes

    4. **Measure and optimize** - Monitor performance and adjust


    Conclusion


    Server Actions have simplified our backend architecture significantly. The reduction in boilerplate, improved type safety, and better developer experience make them our default choice for server interactions in Next.js applications.


    For teams building with Next.js 14+, we highly recommend exploring Server Actions as a modern alternative to traditional API routes.


    E

    Engineering Team

    The Engineering Team at Senpai Software shares insights and best practices from real-world software development projects.