The T3 Stack is a web development stack that focuses on simplicity, modularity, and full-stack type safety. It is made up of the following technologies:
- Next.js: A React framework that makes it easy to build server-rendered and static web pages.
- TypeScript: A typed superset of JavaScript that helps to prevent errors and improve code quality.
- Tailwind CSS: A utility-first CSS framework that makes it easy to style your web applications.
- Prisma: An ORM that makes it easy to interact with your database.
- tRPC: tRPC stands for TypeScript Remote Procedure Call. It is a lightweight, type-safe RPC framework for TypeScript. tRPC allows you to easily build and consume APIs without the need for schemas or code generation.
There are many reasons to use the T3 Stack for your next web development project. Here are a few of the most compelling reasons:
- Full-stack type safety: The T3 Stack uses TypeScript, which provides full-stack type safety. This means that you can catch errors at compile time, which helps to improve code quality and prevent bugs.
- Modularity: The T3 Stack is modular, which means that you can choose the technologies that are right for your project. This makes it easy to scale your application and add new features.
- Maintainability: The T3 Stack is easy to maintain. The use of TypeScript helps to prevent errors and improve code quality. The modularity of the stack makes it easy to add new features and scale your application.
PlanetScale is a serverless database designed to be scalable, secure, and user-friendly. It is built upon Vitess, an open-source database technology originally developed by YouTube. PlanetScale offers an excellent free plan, allowing you to have one MySQL database with a maximum of 1 billion row reads and 10 million row writes each month. It’s an excellent choice if you are seeking a database service for your personal project.
For the authentication, we will use NextAuth with JWT strategy. This approach provides a secure and efficient way to manage user authentication for your application. This method simplifies the user authentication by eliminating the need for session management on the server side. JWTs facilitate secure transmission of user information, granting users seamless and authenticated access to protected resources.
Now, let’s begin to set up this project.
Create a T3 stack project
First, we will initiate our project with T3 stack command.
You have to answer some command prompts. Make sure you choose to use TypeScript and enable nextAuth
, prisma
, tailwind
, and tRPC
packages.
Setup Planetscale
To proceed with this guide, you will require a PlanetScale account. Follow these instructions to establish a database:
- Go to your organization’s overview page and click on “New database” > “Create a database.”
- Assign a name to your database using lowercase letters, numbers, or underscores. While you can use dashes, it’s better to avoid them due to potential complications with sharded databases.
- Choose a region. For optimal performance, select a region close to your location or your application’s hosting site.
- Select the suitable plan type for your database.
- Provide valid credit or debit card details. If you’re opting for the Hobby plan, no charges will be applied.
- Finally, click the “Create database” button to initiate the deployment of your database.
Your database will be created with a default production branch named “main.” This branch will be utilized for implementing schema changes and data insertion. While this is the initial branch provided, you can always generate new development branches (independent replicas of the production schema) based on the production branch for developmental purposes.
Make sure you first have downloaded and installed the PlanetScale CLI. To authenticate with the PlanetScale CLI, enter the following:
You’ll be taken to a screen in the browser where you’ll be asked to confirm the code displayed in your terminal. If the confirmation codes match, click the “Confirm code” button in your browser.
You should receive the message “Successfully logged in” in your terminal. You can now close the confirmation page in the browser and proceed in the terminal.
Configure prisma schema
Next, we need to set up our database schema using Prisma. We will create a User model to store the id
, username
, email
, password
, and fullname
.
Then, because we are using planetscale as our database services, inside datasource db
, set provider to mysql
and relationMode
to prisma
.
Therefore, our schema.prisma
file will look like this:
Afterwards, you should adjust your environment settings to configure the Prisma data source. Locate the .env
file in your application’s main directory and overwrite its current content with this:
Now you should establish a local connection to your PlanetScale database branches through the PlanetScale CLI. In a different terminal window, execute these commands:
Maintain this connection active. This sets up a safe link to PlanetScale and is active on the designated local port. In the terminal, you will receive a local address. This can be utilized to connect to a MySQL client, if desired.
The next step involves pushing your Prisma schema. Open a new tab in your terminal and execute this command:
When working with Prisma and PlanetScale, it’s advisable to follow the workflow of using prisma db push
instead of prisma migrate
. You can find additional information about [prisma db push
here.
Upon successful completion, your PlanetScale database schema will align with the Prisma schema you set up in prisma/schema.prisma
.
Configure NextAuth
There are a couple steps to set up our JWT authentication flow within NextAuth.
Define response and session type
The code above defines a TypeScript type called APIResponse
, which represents the structure of responses from APIs in the application. It consists of three main parts: the status
property indicating if the response was successful or encountered an error, the message
property providing a human-readable description of the response, and the optional data
property that holds the actual response data, which can vary in type based on the context. This type helps maintain a consistent format for API responses throughout the codebase, ensuring that they can be easily understood and processed.
The code segment includes TypeScript type definitions within the next-auth.d.ts
file. It introduces a custom type named LoginResponseData
, outlining the structure of data received after a successful login attempt, encompassing fields such as username, email, access token, and ID. Additionally, the file extends the existing Session
interface from the ‘next-auth’ module. It adds a user
property to the session, associated with the LoginResponseData
type, enabling the storage of authenticated user data within session objects. This allows for consistent handling of user-related information across the application’s authentication flows.
Use NextAuth Credentials Provider
- The
providers
section indicates that the authentication method being used is theCredentialsProvider
. This provider allows users to authenticate using a username and password. Theauthorize
function within the provider handles the authentication process. When a user attempts to authenticate, the credentials they provide are sent to an API endpoint responsible for login. If the credentials are valid and the response is successful, the user object is returned. Otherwise, if the response indicates an error or unsuccessful authentication,null
is returned. - The
callbacks
section defines callback functions that are triggered during different stages of the authentication process. Thejwt
callback is responsible for combining the JSON Web Token (token
) with the user object (user
). This merged data is used for subsequent requests to authenticate the user. Thesession
callback attaches the user data from the token to the session. - The
secret
field contains a sensitive secret value used for cryptographic operations related to authentication. This value is loaded from theenv.NEXTAUTH_SECRET
. - The
session
section specifies the strategy used for managing user sessions, and in this case, it uses the ‘jwt’ strategy. This means that JSON Web Tokens are utilized to maintain session information. - The
pages
section customizes the URLs for authentication-related pages. Specifically, it sets the URL for the sign-in page to ‘/login’.
Configure tRPC middleware
Inside src/server/api/trpc.ts
, you can remove the prisma
from createInnerTRPCContext
since you are not using a session strategy that maintains authentication inside the database.
We also need to update our tRPC middleware as follows:
This middleware is applied to TRPC methods to ensure that a user is authenticated before proceeding. It achieves this by checking if the session
exists and contains a user
property. If not, it throws a TRPCError
with the code ‘UNAUTHORIZED’, indicating that the user lacks proper authentication. If authentication is successful, the middleware allows the method to continue executing and passes along an updated context that ensures the presence of the session
object with a non-null user
property. This middleware plays a vital role in enforcing user authentication across TRPC methods, safeguarding routes that require authorized access.
The complete setup for our trpc will looks like this.
Create API Route for login and register user
The /api/login
route handles user authentication by processing a POST request with the provided username and password. It utilizes JWT (JSON Web Tokens) for authentication. It retrieves the username and password from the request body. We use Prisma to query the database for a user with the provided username. If a user is found, it proceeds to compare the provided password with the hashed password stored in the database using bcrypt. If the passwords match, a JWT is generated and signed with the user’s information, and an access token is included in the response, along with the user’s ID, username, and email.
This /api/register
route handles user registration by verifying provided details, hashing the password, and creating a new user record in the database. It returns relevant success or error responses based on the outcome of the registration process.
Testing
User login and register
To register a new user, create a registration page that accepts input for username, email, password, and confirmation password. Send this data to the /api/register
route.
For user login, you’ll need a login page that takes the username and password as inputs. Then, utilize the signIn()
function provided by next-auth
, passing the username and password as arguments. Detailed information about this function can be found in the next-auth
documentation.
tRPC query and mutation
Querying and mutating data using tRPC is straightforward, akin to using TanStack Query. Here’s an example of data querying:
And for data mutation:
Querying and mutating data in protected routes is nearly identical to how it’s done in public routes. Authentication is automatically managed by tRPC and NextAuth.
However, differences arise when you attempt to access user credentials within your tRPC route. Here’s how you can retrieve the username and user ID from the tRPC context: