Logo
BlogCategoriesChannels

Is SQL in React Components Safe?

Discover the truth behind the SQL in React drama and learn secure patterns for modern web development.

Theo - t3․ggTheo - t3․ggJune 17, 2024

This article was AI-generated based on this episode

What sparked the SQL in React drama?

The SQL in React drama kicked off with an initial tweet by Adam Rackus. At NextConf, Sam Selikoff showcased a compelling pattern in his presentation.

Selikoff demonstrated using a button with a form action that inserts a value into bookmarks. While this was intended to showcase composable patterns, it led to widespread outrage.

The community's reaction was notably intense. Concerns immediately arose about potential SQL injections and the mixing of server-side code in client code. The primary misconception was the assumption that SQL queries were being controlled by the client, leading to security vulnerabilities. In reality, the server-side execution of the queries ensures their safety.

Why is SQL in React components considered safe?

'Use server' ensures server-side execution:

  • The use server directive binds server-side actions to client-side interactions.
  • This means the actual SQL code never runs on the client.
  • The client only sends a request; the server handles execution.

Sanitizing SQL queries with template tag functions:

  • JavaScript's template tag functions help sanitize SQL queries.
  • By splitting strings and variables, potential malicious inputs are neutralized.
  • This separation mitigates risks of SQL injection.

Security measures in place:

  • Only the endpoint URL is exposed to the client, not the SQL code.
  • Data is sanitized before execution, ensuring it’s safe from injections.
  • The components remain secure and maintain the separation between client and server.

How does 'use server' work in Next.js?

The use server directive in Next.js creates backend endpoints that the frontend can hit. It ensures actions tied to client-side interactions execute on the server.

Here's a simple example:

import { useServer } from 'next/server';

async function deletePostAction(postId) {
  useServer();
  console.log(`Deleting post with ID: ${postId}`);
  // Perform your server-side logic here.
}

// Usage in a component
function Post({ post }) {
  return (
    <form action={deletePostAction.bind(null, post.id)}>
      <button type="submit">Delete Post</button>
    </form>
  );
}

When the button is clicked, use server triggers, binding the action to the server. The form posts the data to the server-side function, executing securely away from client vulnerabilities.

This method keeps SQL code off the client while maintaining secure server actions in React. Implementing use server ensures robust security and maintains separation of concerns.

What are the best practices for separating concerns in modern web development?

Separating concerns is crucial in modern web development. It ensures code clarity, maintainability, and security. Here are the best practices to achieve it:

  • Utilize ORMs:

    • Use ORMs like Drizzle ORM or Prisma for database interactions.
    • They provide a structured approach to manage database queries securely.
  • Implement Data Access Layers:

    • Create a separate data access layer to handle all database interactions.
    • This centralizes data logic and isolates it from the UI.
  • Leverage Server Actions:

    • Use server actions in React to keep server-side logic separated from client-side code.
    • use server in Next.js binds client-side actions securely to backend logic.

Flexible Architecture:

  • The new model allows developers to choose their architecture.
  • They can mix and match patterns like SQL in components, HTTP APIs, and data access layers.
  • This flexibility empowers developers to tailor their solutions according to their needs.

Adopting these practices ensures secure and efficient web development, aligning with secure web development patterns and enhancing the maintainability of your application.

How can you implement a data access layer in React?

Creating a data access layer (DAL) in React ensures secure and efficient data handling. Here's a step-by-step guide:

  1. Set up a new file for the data layer:

    // src/dataLayer/index.js
    import { serverOnly } from 'your-server-only-package';
    import { db } from 'your-database-setup';
    
    serverOnly();
    
    export const dataLayer = {
      queries: {
        getPosts: async () => {
          return await db.query.posts.findMany();
        },
      },
      mutations: {
        deletePost: async (postId) => {
          return await db.delete.posts.where({ id: postId });
        },
      },
    };
    
  2. Use the data layer in your component:

    // src/components/PostList.js
    import { dataLayer } from '../dataLayer';
    
    function PostList() {
      const posts = await dataLayer.queries.getPosts();
    
      const handleDelete = async (postId) => {
        await dataLayer.mutations.deletePost(postId);
        // Refresh posts or update state
      };
    
      return (
        <div>
          {posts.map((post) => (
            <div key={post.id}>
              <p>{post.name}</p>
              <button onClick={() => handleDelete(post.id)}>Delete</button>
            </div
    

What role does tRPC play in server actions?

tRPC is a powerful tool for managing server actions in React. It helps in validating inputs and ensures your server-side logic is robust and secure.

Here's how tRPC can be integrated with server actions:

First, define a server action procedure using tRPC:

import { initTRPC } from '@trpc/server';

const t = initTRPC();

export const createPost = t.procedure
  .input({
    title: 'string',
  })
  .mutation(async ({ input }) => {
    // Logic to create a post
    console.log(`Creating post with title: ${input.title}`);
    return { success: true };
  });

Next, bind this procedure to a form in your React component:

function CreatePostForm() {
  const createPostAction = trpc.createPost.useMutation();

  const handleSubmit = (event) => {
    event.preventDefault();
    const title = event.target.elements.title.value;
    createPostAction.mutate({ title });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" type="text" required />
      <button type="submit">Create Post</button>
    </form>
  );
}

Benefits of using tRPC with server actions:

  • Validation: Automatically validates inputs.
  • Security: Ensures server-side code is executed securely.

What are the different ways to model data in React?

Modeling data in React can be approached in various ways. Here are three recommended patterns:

  1. Direct SQL in Components:

    Pros:

    • Easy to prototype and learn.
    • Reduces the need for multiple files and setups.

    Cons:

    • Not ideal for production.
    • May lead to security issues if not sanitized properly.
    • Difficult to maintain as the app grows.
  2. HTTP APIs:

    Pros:

    • Allows use of existing backend services.
    • Keeps the frontend lightweight.
    • Easy to scale.

    Cons:

    • Requires additional communication overhead.
    • May complicate state management and data fetching.
    • Potentially increased latency.
  3. Data Access Layers:

    Pros:

    • Centralizes database interactions.
    • Ensures code clarity and maintainability.
    • Enhances security by isolating data logic.

    Cons:

    • Requires an initial setup effort.
    • Adds complexity for simple apps.
    • Can lead to over-engineering if not used properly.

Choose the pattern that best fits your project's needs. Each approach offers its own set of benefits and challenges to consider.

FAQs

Loading related articles...