Do somthings

In modern application development, seamless data storage and retrieval are crucial. By combining Flutter for frontend development and PostgreSQL as the database, you can build scalable, secure, and high-performing apps.

Why Use Flutter and PostgreSQL Together?

  • Flutter: A robust UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase.
  • PostgreSQL: A powerful, open-source relational database system known for its performance, scalability, and support for complex queries.

Benefits of Connecting Flutter with PostgreSQL

  1. Real-time Data Management: PostgreSQL’s ACID compliance ensures data reliability.
  2. Ease of Integration: Flutter can communicate with PostgreSQL using a backend API.
  3. Security: PostgreSQL supports advanced authentication and encryption methods.
  4. Scalability: Handles complex data relationships and large volumes of data efficiently.

Step-by-Step Guide

This guide covers how to create a connection between Flutter and PostgreSQL using a backend API.

Project Overview

  1. Backend: A Node.js server to interact with PostgreSQL.
  2. Flutter App: Includes pages for registration, login, and homepage.
  3. Database: PostgreSQL to store user information.

Step 1: Set Up PostgreSQL Database

  1. Install PostgreSQL on your system.
  2. Create a new database:
createdb flutter_db

3. Create a users table:

CREATE TABLE users (
    userid SERIAL PRIMARY KEY,
    firstname VARCHAR(50),
    lastname VARCHAR(50),
    email VARCHAR(100) UNIQUE NOT NULL,
    phonenumber VARCHAR(15),
    password VARCHAR(255) NOT NULL
);

Step 2: Build the Backend with Node.js

  1. Initialize a Node.js project:
mkdir flutter_postgres_backend && cd flutter_postgres_backend
npm init -y

2. Install dependencies:

npm install express body-parser pg bcrypt dotenv helmet express-validator

3. Create a .env file to store database credentials:

DB_USER=your_username
DB_HOST=localhost
DB_DATABASE=flutter_db
DB_PASSWORD=your_password
DB_PORT=5432

4. Write the server code (server.js):

require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const { Pool } = require('pg');
const bcrypt = require('bcrypt');
const { body, validationResult } = require('express-validator');
const helmet = require('helmet');

const app = express();
app.use(bodyParser.json());
app.use(helmet());

const pool = new Pool({
    user: process.env.DB_USER,
    host: process.env.DB_HOST,
    database: process.env.DB_DATABASE,
    password: process.env.DB_PASSWORD,
    port: process.env.DB_PORT,
});

// Register User
app.post('/register', [
    body('firstname').isString().notEmpty(),
    body('lastname').isString().notEmpty(),
    body('email').isEmail(),
    body('phonenumber').isMobilePhone(),
    body('password').isLength({ min: 6 })
], async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
    }

    const { firstname, lastname, email, phonenumber, password } = req.body;

    try {
        const hashedPassword = await bcrypt.hash(password, 10);

        const result = await pool.query(
            `INSERT INTO users (firstname, lastname, email, phonenumber, password)
             VALUES ($1, $2, $3, $4, $5) RETURNING *`,
            [firstname, lastname, email, phonenumber, hashedPassword]
        );

        res.status(201).json({ message: 'User registered successfully', user: result.rows[0] });
    } catch (error) {
        console.error(error);
        res.status(500).json({ error: 'Database error' });
    }
});

// Login User
app.post('/login', [
    body('email').isEmail(),
    body('password').isLength({ min: 6 })
], async (req, res) => {
    const { email, password } = req.body;

    try {
        const result = await pool.query(`SELECT * FROM users WHERE email = $1`, [email]);

        if (result.rows.length === 0) {
            return res.status(404).json({ error: 'User not found' });
        }

        const user = result.rows[0];
        const isPasswordValid = await bcrypt.compare(password, user.password);
        if (!isPasswordValid) {
            return res.status(401).json({ error: 'Invalid credentials' });
        }

        res.status(200).json({ message: 'Login successful', user });
    } catch (error) {
        console.error(error);
        res.status(500).json({ error: 'Database error' });
    }
});

app.listen(3000, () => console.log('Server running on port 3000'));

Step 3: Create the Flutter App

  1. Create a Flutter project:
flutter create flutter_postgres_app

2. Install dependencies:

dependencies:
  http: ^0.15.0
  shared_preferences: ^2.0.13

<strong>main.dart</strong>

Entry point of the app:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  SharedPreferences prefs = await SharedPreferences.getInstance();
  bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
  runApp(MyApp(isLoggedIn: isLoggedIn));
}

class MyApp extends StatelessWidget {
  final bool isLoggedIn;

  MyApp({required this.isLoggedIn});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: isLoggedIn ? Homepage() : LoginPage(),
    );
  }
}

loginPage.dart

import 'package:flutter/material.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'package:for_web_testing/Database/PostGre.dart';
import 'package:for_web_testing/Pages/HomePage.dart';
import 'package:for_web_testing/Pages/registerPage.dart';
import 'package:shared_preferences/shared_preferences.dart';

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {

  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final DatabaseConnection _databaseConnection = DatabaseConnection();

  void _loginUser() async {
    try {
      final hashedPassword = _hashPassword(_passwordController.text);

      final response = await _databaseConnection.loginUser(
        email: _emailController.text,
        password: hashedPassword,
      );

      if (response['success']) {

        SharedPreferences prefs = await SharedPreferences.getInstance();
        await prefs.setBool('isLoggedIn', true);
        await prefs.setString('userId', response['userId'].toString()); // Save user ID

        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: Text('Login successful!'),
          backgroundColor: Colors.green,
        ));

        Future.delayed(Duration(seconds: 3), () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => Homepage()),
          );
        });
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(response['message'] ?? 'Login failed.')),
        );
      }
    } catch (e) {
      print('Error: $e');
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('An error occurred: $e')),
      );
    }
  }

  // Hash the password using SHA-256
  String _hashPassword(String password) {
    final bytes = utf8.encode(password); // Convert to UTF-8
    final hashedPassword = sha256.convert(bytes).toString(); // Hash the password
    return hashedPassword;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _emailController,
              decoration: InputDecoration(labelText: 'Email'),
            ),
            TextField(
              controller: _passwordController,
              decoration: InputDecoration(labelText: 'Password'),
              obscureText: true,
            ),
            SizedBox(height: 20),
            Row(
              children: [
                ElevatedButton(
                  onPressed: _loginUser,
                  child: Text('Login'),
                ),
                TextButton(
                    onPressed: (){
                      Navigator.push(context, MaterialPageRoute(builder: (context)=>RegisterPage()));
                    },
                    child: Text('Register Page'),
                )
              ],
            ),
          ],
        ),
      ),
    );
  }

}

Registerpage.dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:for_web_testing/Database/PostGre.dart';
import 'package:for_web_testing/Pages/LoginPage.dart';
import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart';

class RegisterPage extends StatefulWidget {
  @override
  _RegisterPageState createState() => _RegisterPageState();
}

class _RegisterPageState extends State<RegisterPage> {
  final TextEditingController _firstnameController = TextEditingController();
  final TextEditingController _lastnameController = TextEditingController();
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _phoneController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  final DatabaseConnection _databaseConnection = DatabaseConnection();

  void _registerUser() async {
    try {
      // Hash the password before sending it to the database
      final hashedPassword = _hashPassword(_passwordController.text);

      // Get the response from the registerUser method
      final response = await _databaseConnection.registerUser(
        firstname: _firstnameController.text,
        lastname: _lastnameController.text,
        email: _emailController.text,
        phoneNumber: _phoneController.text,
        password: hashedPassword,
      );

      // Check if the registration was successful
      if (response['success'] == true) {

        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
            content: Text('Registration successful!'),
            backgroundColor: Colors.green,
          ));

        Future.delayed(Duration(seconds: 3),(){

          //START...
          Navigator.push(context, MaterialPageRoute(builder: (context)=>LoginPage()));

        });

        // Optionally clear input fields after successful registration...
        _firstnameController.clear();
        _lastnameController.clear();
        _emailController.clear();
        _phoneController.clear();
        _passwordController.clear();

      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Registration failed: ${response['message']}'),
            backgroundColor: Colors.red,
          ),
        );
      }
    } catch (e) {
      // Handle any unexpected errors
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('An error occurred: $e'),
          backgroundColor: Colors.red,
        ),
      );
    }
  }

  // Method to hash the password using SHA-256
  String _hashPassword(String password) {
    final bytes = utf8.encode(password); // Convert to UTF-8
    final hashedPassword = sha256.convert(bytes).toString(); // Hash the password
    return hashedPassword;
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Register')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _firstnameController,
              decoration: InputDecoration(labelText: 'First Name'),
            ),
            TextField(
              controller: _lastnameController,
              decoration: InputDecoration(labelText: 'Last Name'),
            ),
            TextField(
              controller: _emailController,
              decoration: InputDecoration(labelText: 'Email'),
            ),
            TextField(
              controller: _phoneController,
              decoration: InputDecoration(labelText: 'Phone Number'),
            ),
            TextField(
              controller: _passwordController,
              decoration: InputDecoration(labelText: 'Password'),
              obscureText: true,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _registerUser,
              child: Text('Register'),
            ),
          ],
        ),
      ),
    );
  }

}

HomePage.dart

import 'package:flutter/material.dart';
import 'package:for_web_testing/Database/PostGre.dart';
import 'package:shared_preferences/shared_preferences.dart';

class Homepage extends StatefulWidget {
  const Homepage({super.key});

  @override
  State<Homepage> createState() => _HomepageState();
}

class _HomepageState extends State<Homepage> {

  String username = '';
  String phoneNumber = '';
  String email = '';
  bool isLoading = true;

  final DatabaseConnection _databaseConnection = DatabaseConnection();

  @override
  void initState() {
    super.initState();
    _fetchUserDetails();
  }

  Future<void> _fetchUserDetails() async {
    try {
      SharedPreferences prefs = await SharedPreferences.getInstance();
      String? userId = prefs.getString('userId');

      if (userId == null) {
        setState(() {
          isLoading = false;
          username = 'Unknown';
          phoneNumber = 'Unknown';
          email = 'Unknown';
        });
        return;
      }

      final response = await _databaseConnection.fetchUserDetails(userId: userId);

      if (response['success']) {
        setState(() {
          username = '${response['firstname']} ${response['lastname']}';
          phoneNumber = response['phonenumber'];
          email = response['email'];
          isLoading = false;
        });
      }
      else {
        setState(() {
          username = 'Unknown';
          phoneNumber = 'Unknown';
          email = 'Unknown';
          isLoading = false;
        });
        print('Failed to fetch user details: ${response['message']}');
      }

    } catch (e) {
      print('Error: $e');
      setState(() {
        username = 'Unknown';
        phoneNumber = 'Unknown';
        email = 'Unknown';
        isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Home Page"),
        leading: IconButton(onPressed: () {}, icon: const Icon(Icons.menu)),
      ),
      body: isLoading
          ? const Center(child: CircularProgressIndicator())
          : Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              "HOME PAGE",
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            Text("User Name: $username"),
            Text("Phone Number: $phoneNumber"),
            Text("Email ID: $email"),
          ],
        ),
      ),
    );
  }
}

Output:

Conclusion:

By following this guide, you have successfully created a seamless connection between a Flutter application and a PostgreSQL database using a Node.js backend. This setup allows you to leverage the strengths of Flutter for creating intuitive user interfaces and PostgreSQL for robust data management. As you expand your application, this architecture ensures scalability, security, and performance. With the flexibility of Flutter and the reliability of PostgreSQL, you are well-equipped to develop high-quality, full-stack applications.

❤️❤️ Thanks for reading this article ❤️❤️

If I got something wrong? Let me know in the comments. I would love to improve 🥰🥰🥰.

Clap 👏👏👏 If this article helps you,

if you like our work, please follow us on this Dosomthings

Our more attractive articles:

Refresh in FlutterHow to implement Pull to Refresh in Flutter?

Date & Time in FlutterHow to Format Date and Time in Flutter

Drag and Drop in FlutteHow to Implement Drag and Drop in Flutter