13.1 - Defining Routers and Procedures
What is the Backend?
The backend refers to the part of a software application that runs on the server, hidden from the user's view. It handles the business logic, database operations, and communication with other systems, ensuring the application functions as intended. While the frontend is what users interact with directly (like buttons, forms, and pages), the backend is the engine that powers everything behind the scenes.
Key Components of the Backend
-
Server
- A server is a computer or a system that provides resources, data, services, or programs to other computers, known as clients, over a network.
- In the context of a web application, the server hosts the backend logic and serves content to users via the internet.
- Common server types include web servers (like Apache or Nginx) and application servers (like Node.js or Django).
-
Database
- A database is a system that stores and manages data. It's where the application's information, like user accounts, posts, and comments, is kept.
- The backend interacts with the database to retrieve, store, update, or delete data as requested by the client.
- Examples of databases include relational databases like MySQL and PostgreSQL, or NoSQL databases like MongoDB.
-
API (Application Programming Interface)
- An API is a set of rules and definitions that allows one piece of software to communicate with another.
- In the context of the backend, an API allows the frontend (like a web page or mobile app) to interact with the server. For example, when you submit a form on a website, the data is sent to the server via an API.
- APIs can be designed in various ways, such as REST, GraphQL, or tRPC, each with its own structure and use cases.
How Does the Backend Work?
When a user interacts with an application, several things happen in the backend:
-
User Request
- A user makes a request from the frontend, such as clicking a button to view their profile. This request is sent to the backend server through an API.
-
Processing the Request
- The server receives the request and processes it. This may involve:
- Business Logic: The server applies the rules of the application (e.g., checking if the user is authorized to view certain content).
- Database Interaction: If the request requires data (e.g., retrieving user details), the server queries the database.
- The server receives the request and processes it. This may involve:
-
Generating a Response
- Once the request is processed, the server generates a response. This could be data (like user details) or a confirmation that an action was completed (like updating a profile).
-
Sending the Response
- The server sends the response back to the frontend via the API. The frontend then displays the data or confirms the action to the user.
Real-World Example: A Social Media App
Consider a social media app where users can post updates, comment on posts, and like content. Here’s how the backend might work:
-
Posting an Update
- Request: The user writes a post and hits "Submit." The frontend sends the post data to the server.
- Processing: The server checks if the user is logged in, then stores the post in the database.
- Response: The server confirms that the post was successfully saved and sends this confirmation to the frontend.
-
Viewing a Profile
- Request: A user clicks on another user's profile. The frontend sends a request to the server to get the profile information.
- Processing: The server retrieves the profile data from the database.
- Response: The server sends the profile data back to the frontend, which displays it to the user.
Preparing to Learn tRPC
Understanding these backend fundamentals sets the stage for learning more advanced topics like tRPC. With tRPC, you can build a type-safe API where the frontend and backend communicate seamlessly without the need for manual API client generation. In the next sections, we'll dive into how to define routes and procedures in tRPC, and how to leverage its powerful features to build robust, scalable applications.
Introduction to tRPC
Overview
tRPC (TypeScript Remote Procedure Call) is a framework that enables building end-to-end type-safe APIs using TypeScript. It allows you to define your API contract using TypeScript types and provides type inference across the client and server. The main goal of tRPC is to eliminate the need for manual API client generation and ensure type safety throughout the development process.
How it Works
- Define Procedures on the Server: Use TypeScript to define API endpoints (procedures) for queries and mutations.
- Generate Type-Safe Client: tRPC generates a type-safe client that you can use in your frontend code.
- Invoke Procedures from the Client: Call the server-side procedures from the client with full type safety and autocompletion.
Features
- Type Safety: Automatically infers types from your server to your client, reducing the risk of runtime errors.
- No Schema Definitions: Unlike GraphQL or REST, tRPC does not require separate schema definitions. It uses TypeScript types directly.
- Flexible and Lightweight: Provides a simple API to define and use procedures, making it easy to integrate with existing projects.
- Middleware Support: Allows for adding middleware for authentication, authorization, logging, etc.
Defining Routers and Procedures
In tRPC, the two fundamental concepts you'll work with on the server are routers and procedures.
Routers
A router in tRPC is a way to group related API procedures, much like a controller in traditional MVC (Model-View-Controller) frameworks. Routers help you organize your API endpoints logically, making your codebase easier to maintain.
Example of a Router
Imagine you're building an API for a blogging platform. You might have different routers for handling users, posts, and comments. Here's a basic example:
import { router } from "@trpc/server";
import { z } from "zod"; // for input validation
const userRouter = router({
getUser: procedure.input(z.object({ id: z.string() })).query(({ input }) => {
// Logic to fetch user by ID
return getUserFromDatabase(input.id);
}),
createUser: procedure
.input(z.object({ name: z.string(), email: z.string() }))
.mutation(({ input }) => {
// Logic to create a new user
return createUserInDatabase(input);
}),
});
In this example, the userRouter
contains two procedures: getUser
and createUser
. These procedures are defined using tRPC's procedure
method, and each procedure can have its own input and output types.
Procedures
Procedures are the actual endpoints in your API. They represent a specific piece of functionality that your frontend can call, like fetching data or creating a new resource. Procedures in tRPC are type-safe, meaning the input and output are strongly typed.
Types of Procedures
-
Queries: Used for fetching data without causing side effects. For example, getting a user's profile or retrieving a list of posts.
-
Mutations: Used for operations that modify data on the server. For example, creating a new post, updating user information, or deleting a comment.
Example of Procedures
Continuing with the blogging platform example, let's add some procedures to handle posts:
const postRouter = router({
getPost: procedure.input(z.object({ id: z.string() })).query(({ input }) => {
// Logic to fetch a post by ID
return getPostFromDatabase(input.id);
}),
createPost: procedure
.input(z.object({ title: z.string(), content: z.string() }))
.mutation(({ input }) => {
// Logic to create a new post
return createPostInDatabase(input);
}),
});