In this article, I'll share my journey of integrating Supabase with Ballerina as a backend and Next.js as the frontend framework to create a modern authentication system. This approach not only allows for a streamlined user experience but also leverages the power of JWT (JSON Web Tokens) for secure authentication.
Overview of the Tech Stack
- Supabase: An open-source Firebase alternative that provides a backend-as-a-service, including authentication, database management, and real-time functionalities.
- Ballerina: A programming language specifically designed for cloud-native application development, making it perfect for handling API integrations and microservices.
- Next.js: A React-based framework that enables server-side rendering and static site generation, providing an excellent user experience.
Setting Up the Project
1. Initializing Supabase
I started by creating a Supabase project and setting up the database. I defined a users
table with fields for the username and password. The password would be stored securely as a hash.
2. Connecting Ballerina to Supabase
In Ballerina, I created a service to handle user registration and login. Here’s a snippet of the code to establish a connection to the Supabase PostgreSQL database:
import ballerina/http;
import ballerinax/postgresql;
// Supabase DB connection config
configurable string host = "your_host";
configurable int port = 5432;
configurable string username = "your_username";
configurable string password = "your_password";
configurable string databaseName = "your_database";
service /auth on new http:Listener(9090) {
final postgresql:Client dbClient;
public function init() returns error? {
self.dbClient = check new (host, username, password, databaseName, port);
}
}
3. User Registration and Hashing Passwords
For user registration, I utilized the crypto
module in Ballerina to hash the passwords securely. Here's how I handled user registration:
resource function post register(http:Caller caller, http:Request req) returns error? {
json payload = check req.getJsonPayload();
string username = (check payload.username).toString();
string plainPassword = (check payload.password).toString();
// Hash the password
byte[] hashedPassword = check crypto:hashSha256(plainPassword.toBytes());
// Insert the user into the database
sql:ExecutionResult result = check self.dbClient->execute(
`INSERT INTO users (username, password) VALUES (${username}, ${hashedPassword.toBase16String()})`
);
if result.affectedRowCount == 1 {
check caller->respond({ message: "Registration successful" });
} else {
check caller->respond({ message: "Registration failed" });
}
}
4. User Login and JWT Generation
For the login functionality, I queried the database to retrieve the hashed password, compared it with the entered password, and generated a JWT token upon successful authentication:
resource function post login(http:Caller caller, http:Request req) returns error {
json payload = check req.getJsonPayload();
string username = (check payload.username).toString();
string plainPassword = (check payload.password).toString();
// Retrieve the user from the database
stream<record {|anydata...;|}, sql:Error?> resultStream = self.dbClient->query(
`SELECT password FROM users WHERE username = ${username}`
);
// Verify the password and generate JWT token
// (Code to validate the password and generate JWT)
}
5. Implementing the Frontend with Next.js
On the frontend, I built a registration page using React and styled it with Tailwind CSS. The form captures the username and password, sending the data to the Ballerina backend for processing.
Here’s a snippet of the registration form component:
import React, { useState } from 'react';
const RegisterPage = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = async (event) => {
event.preventDefault();
const response = await fetch('/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const data = await response.json();
// Handle response...
};
return (
<form onSubmit={handleSubmit}>
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
<button type="submit">Register</button>
</form>
);
};
export default RegisterPage;
Conclusion
This project has taught me the intricacies of handling authentication securely and effectively using modern technologies. Integrating Supabase with Ballerina and Next.js has provided me with a robust solution for user management.
I hope this article inspires you to explore similar integrations and improve your skills in building modern web applications. If you have any questions or feedback, feel free to reach out!