Full Stack Development

Validations and Error Handling



by Tomas Trescak t.trescak@westernsydney.edu.au

Next.js

/

Validations and Errors

  • Validation keeps your app safe
  • Error handling improves user experience
  • With Zod you define data rules easily
  • Catch mistakes early
    import { z } from "zod";

const postSchema = z.object({
  title: z.string().min(1, "Title is required"),
  content: z.string().min(10, "Content must be at least 10 characters"),
  tags: z.array(z.string()).min(1, "At least one tag is required"),
});

const data = { title: "", content: "Hi", tags: [] };
const result = postSchema.safeParse(data);
// Returns { success: false, error: { title: "Title is required", ... } }
  

Next.js

/

Validations and Errors

  • Use in APIs and server actions
  • Return errors, not crashes
    "use server";
import { z } from "zod";

const postSchema = z.object({
  title: z.string().min(1, "Title is required"),
  content: z.string().min(10, "Content must be at least 10 characters"),
  tags: z.array(z.string()).min(1, "At least one tag is required"),
});

export async function createPost(formData: FormData) {
  const data = {
    title: formData.get("title") as string,
    content: formData.get("content") as string,
    tags: formData.get("tags")?.toString().split(",") || [],
  };

  const result = postSchema.safeParse(data);
  if (!result.success) {
    return { error: result.error.format() };
  }

  // Simulate saving to DB
  console.log("Saving post:", result.data);
  return { success: true };
}
  

Next.js

/

Errors vs Exceptions

  • Errors : Expected issues
  • Exceptions : Unexpected problems

Next.js

/

TanStack Query on the Client

    import { useMutation } from "@tanstack/react-query";

async function createPost(data: { title: string; content: string; tags: string[] }) {
  const res = await fetch("/api/posts", {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json();
}

export function PostForm() {
  const mutation = useMutation({ 
    mutationFn: createPost,
    onError(e) {
      alert(e.message);
      setState(e.data)
    }
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.target as HTMLFormElement);
    mutation.mutate({
      title: formData.get("title") as string,
      content: formData.get("content") as string,
      tags: formData.get("tags")?.toString().split(",") || [],
    },);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="Title" />
      <textarea name="content" placeholder="Content" />
      <input name="tags" placeholder="Tags (comma-separated)" />
      <button type="submit">Submit</button>
      {mutation.isError && <p>Error: {mutation.error.message}</p>}
    </form>
  );
}
  

Next.js

/

Takeaways

  • Validate both client-side AND server side
  • Separate errors and exceptions
  • Use TanStack Query for smooth UX