DEV Community

Nebula
Nebula

Posted on

backend

SETUP
✅ What To Do
Go to Spring Initializr
👉 https://start.spring.io/

Fill in the form like this:

Project: Maven (default, like npm for Java)

Language: Java

Spring Boot Version: Use default

Project Metadata:

Group: com.bankapp

Artifact: bankapp

Name: bankapp

Package name: com.bankapp

Packaging: Jar

Java: 17 or 21 (depending on your local setup)

Add Dependencies:

✅ Spring Web (like Express.js)

✅ Spring Data JPA (like Mongoose ORM)

✅ H2 Database (in-memory DB like MongoDB memory server for testing)

✅ Spring Boot DevTools (for hot reloads)

Click Generate → Unzip the project.

Open it in IntelliJ IDEA or VS Code with Java extensions.

✅ 4. Global Exception Handler (Optional but clean)

package com.bankapp.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<?> handleResourceNotFound(ResourceNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationErrors(MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getFieldErrors().forEach(error -> 
        errors.put(error.getField(), error.getDefaultMessage()));
    return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}

}

Enter fullscreen mode Exit fullscreen mode

✅ Fix: Add Jakarta Bean Validation Dependency

<dependency>
  <groupId>jakarta.validation</groupId>
  <artifactId>jakarta.validation-api</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Enter fullscreen mode Exit fullscreen mode

✅ 1. Add JWT Dependency

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.11.5</version>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-impl</artifactId>
  <version>0.11.5</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-jackson</artifactId> <!-- or gson -->
  <version>0.11.5</version>
  <scope>runtime</scope>
</dependency>
Enter fullscreen mode Exit fullscreen mode

✅ 1. Add Spring Security Dependency

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

✅ Step 1: Create the Models (Entities)
USER MODEL

package com.bankapp.model;

import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.List;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @Column(unique = true)
    private String email;

    private String password;

    private LocalDateTime createdAt;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Account> accounts;

    public User() {}

    public User(Long id, String name, String email, String password, LocalDateTime createdAt, List<Account> accounts) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.password = password;
        this.createdAt = createdAt;
        this.accounts = accounts;
    }

    // Getters and setters for all fields
    // You can use your IDE to auto-generate them
}
Enter fullscreen mode Exit fullscreen mode

ACCOUNT MODEL

package com.bankapp.model;

import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Entity
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @Column(unique = true)
    private String accountNumber;

    private String accountType;

    private BigDecimal balance;

    private LocalDateTime createdAt;

    private String status;

    @OneToMany(mappedBy = "fromAccount", cascade = CascadeType.ALL)
    private List<Transaction> sentTransactions;

    @OneToMany(mappedBy = "toAccount", cascade = CascadeType.ALL)
    private List<Transaction> receivedTransactions;

    public Account() {}

    public Account(Long id, User user, String accountNumber, String accountType, BigDecimal balance,
                   LocalDateTime createdAt, String status,
                   List<Transaction> sentTransactions, List<Transaction> receivedTransactions) {
        this.id = id;
        this.user = user;
        this.accountNumber = accountNumber;
        this.accountType = accountType;
        this.balance = balance;
        this.createdAt = createdAt;
        this.status = status;
        this.sentTransactions = sentTransactions;
        this.receivedTransactions = receivedTransactions;
    }

    // Getters and setters for all fields
}
Enter fullscreen mode Exit fullscreen mode

TRANSACTION MODEL

package com.bankapp.model;

import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Entity
public class Transaction {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "from_account_id", nullable = true)
    private Account fromAccount;

    @ManyToOne
    @JoinColumn(name = "to_account_id", nullable = true)
    private Account toAccount;

    private String type;

    private BigDecimal amount;

    @Column(length = 500)
    private String description;

    private LocalDateTime timestamp;

    private String status;

    public Transaction() {}

    public Transaction(Long id, Account fromAccount, Account toAccount, String type, BigDecimal amount,
                       String description, LocalDateTime timestamp, String status) {
        this.id = id;
        this.fromAccount = fromAccount;
        this.toAccount = toAccount;
        this.type = type;
        this.amount = amount;
        this.description = description;
        this.timestamp = timestamp;
        this.status = status;
    }

    // Getters and setters for all fields
}
Enter fullscreen mode Exit fullscreen mode

REPOSITORIES

USER REPOSITORY

package com.bankapp.repository;

import com.bankapp.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
    boolean existsByEmail(String email);
}
Enter fullscreen mode Exit fullscreen mode

ACCOUNT REPOSITORY

package com.bankapp.repository;

import com.bankapp.model.Account;
import com.bankapp.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;

public interface AccountRepository extends JpaRepository<Account, Long> {
    List<Account> findByUser(User user);
    Optional<Account> findByAccountNumber(String accountNumber);
}
Enter fullscreen mode Exit fullscreen mode

TRANSACTION REPOSITORY

package com.bankapp.repository;

import com.bankapp.model.Transaction;
import com.bankapp.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface TransactionRepository extends JpaRepository<Transaction, Long> {
    List<Transaction> findByFromAccount(Account fromAccount);
    List<Transaction> findByToAccount(Account toAccount);
}
Enter fullscreen mode Exit fullscreen mode

----- SERVICES -----
USER SERVICE

package com.bankapp.service;

import com.bankapp.model.User;

import java.util.List;
import java.util.Optional;

public interface UserService {
    User createUser(User user);
    Optional<User> getUserById(Long id);
    Optional<User> getUserByEmail(String email);
    List<User> getAllUsers();
}
Enter fullscreen mode Exit fullscreen mode

USER SERVICE IMPL

package com.bankapp.service.impl;

import com.bankapp.model.User;
import com.bankapp.repository.UserRepository;
import com.bankapp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public User createUser(User user) {
        user.setCreatedAt(LocalDateTime.now());
        return userRepository.save(user);
    }

    @Override
    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }

    @Override
    public Optional<User> getUserByEmail(String email) {
        return userRepository.findByEmail(email);
    }

    @Override
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}
Enter fullscreen mode Exit fullscreen mode

ACCOUNT SERVICE

package com.bankapp.service;

import com.bankapp.model.Account;
import com.bankapp.model.User;

import java.util.List;
import java.util.Optional;

public interface AccountService {
    Account createAccount(Account account);
    Optional<Account> getAccountById(Long id);
    Optional<Account> getByAccountNumber(String accountNumber);
    List<Account> getAccountsByUser(User user);
}
Enter fullscreen mode Exit fullscreen mode

ACCOUNT SERVICE IMPL

package com.bankapp.service.impl;

import com.bankapp.model.Account;
import com.bankapp.model.User;
import com.bankapp.repository.AccountRepository;
import com.bankapp.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Service
public class AccountServiceImpl implements AccountService {

    private final AccountRepository accountRepository;

    @Autowired
    public AccountServiceImpl(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Override
    public Account createAccount(Account account) {
        account.setCreatedAt(LocalDateTime.now());
        return accountRepository.save(account);
    }

    @Override
    public Optional<Account> getAccountById(Long id) {
        return accountRepository.findById(id);
    }

    @Override
    public Optional<Account> getByAccountNumber(String accountNumber) {
        return accountRepository.findByAccountNumber(accountNumber);
    }

    @Override
    public List<Account> getAccountsByUser(User user) {
        return accountRepository.findByUser(user);
    }
}
Enter fullscreen mode Exit fullscreen mode

TRANSACTION SERVICE

package com.bankapp.service;

import com.bankapp.model.Account;
import com.bankapp.model.Transaction;

import java.util.List;

public interface TransactionService {
    Transaction deposit(Account toAccount, double amount, String description);
    Transaction withdraw(Account fromAccount, double amount, String description);
    Transaction transfer(Account fromAccount, Account toAccount, double amount, String description);

    List<Transaction> getTransactionsByAccount(Account account);
}
Enter fullscreen mode Exit fullscreen mode

TRANSACTION SERVICE IMPL

package com.bankapp.service.impl;

import com.bankapp.model.Account;
import com.bankapp.model.Transaction;
import com.bankapp.repository.AccountRepository;
import com.bankapp.repository.TransactionRepository;
import com.bankapp.service.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Service
public class TransactionServiceImpl implements TransactionService {

    private final TransactionRepository transactionRepository;
    private final AccountRepository accountRepository;

    @Autowired
    public TransactionServiceImpl(TransactionRepository transactionRepository,
                                  AccountRepository accountRepository) {
        this.transactionRepository = transactionRepository;
        this.accountRepository = accountRepository;
    }

    @Override
    public Transaction deposit(Account toAccount, double amount, String description) {
        BigDecimal amt = BigDecimal.valueOf(amount);
        toAccount.setBalance(toAccount.getBalance().add(amt));
        accountRepository.save(toAccount);

        Transaction txn = new Transaction(null, null, toAccount, "DEPOSIT", amt, description,
                LocalDateTime.now(), "SUCCESS");
        return transactionRepository.save(txn);
    }

    @Override
    public Transaction withdraw(Account fromAccount, double amount, String description) {
        BigDecimal amt = BigDecimal.valueOf(amount);
        if (fromAccount.getBalance().compareTo(amt) < 0) {
            throw new IllegalArgumentException("Insufficient balance");
        }

        fromAccount.setBalance(fromAccount.getBalance().subtract(amt));
        accountRepository.save(fromAccount);

        Transaction txn = new Transaction(null, fromAccount, null, "WITHDRAWAL", amt, description,
                LocalDateTime.now(), "SUCCESS");
        return transactionRepository.save(txn);
    }

    @Override
    public Transaction transfer(Account fromAccount, Account toAccount, double amount, String description) {
        BigDecimal amt = BigDecimal.valueOf(amount);

        if (fromAccount.getBalance().compareTo(amt) < 0) {
            throw new IllegalArgumentException("Insufficient balance");
        }

        fromAccount.setBalance(fromAccount.getBalance().subtract(amt));
        toAccount.setBalance(toAccount.getBalance().add(amt));

        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);

        Transaction txn = new Transaction(null, fromAccount, toAccount, "TRANSFER", amt, description,
                LocalDateTime.now(), "SUCCESS");
        return transactionRepository.save(txn);
    }

    @Override
    public List<Transaction> getTransactionsByAccount(Account account) {
        List<Transaction> sent = transactionRepository.findByFromAccount(account);
        List<Transaction> received = transactionRepository.findByToAccount(account);
        sent.addAll(received);
        return sent;
    }
}

Enter fullscreen mode Exit fullscreen mode

--------------- CONTROLLERS --------------------

USER CONTROLLER

package com.bankapp.controller;

import com.bankapp.model.User;
import com.bankapp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/create")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User created = userService.createUser(user);
        return ResponseEntity.ok(created);
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getById(@PathVariable Long id) {
        return userService.getUserById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @GetMapping
    public ResponseEntity<List<User>> getAll() {
        return ResponseEntity.ok(userService.getAllUsers());
    }
}
Enter fullscreen mode Exit fullscreen mode

ACCOUNT CONTROLLER

package com.bankapp.controller;

import com.bankapp.model.Account;
import com.bankapp.model.User;
import com.bankapp.service.AccountService;
import com.bankapp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/accounts")
public class AccountController {

    private final AccountService accountService;
    private final UserService userService;

    @Autowired
    public AccountController(AccountService accountService, UserService userService) {
        this.accountService = accountService;
        this.userService = userService;
    }

    @PostMapping("/create/{userId}")
    public ResponseEntity<?> createAccount(@PathVariable Long userId, @RequestBody Account account) {
        return userService.getUserById(userId).map(user -> {
            account.setUser(user);
            Account created = accountService.createAccount(account);
            return ResponseEntity.ok(created);
        }).orElse(ResponseEntity.badRequest().body("User not found"));
    }

    @GetMapping("/user/{userId}")
    public ResponseEntity<List<Account>> getUserAccounts(@PathVariable Long userId) {
        return userService.getUserById(userId).map(user -> {
            List<Account> accounts = accountService.getAccountsByUser(user);
            return ResponseEntity.ok(accounts);
        }).orElse(ResponseEntity.notFound().build());
    }

    @GetMapping("/{accountNumber}")
    public ResponseEntity<?> getAccountByNumber(@PathVariable String accountNumber) {
        return accountService.getByAccountNumber(accountNumber)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
}
Enter fullscreen mode Exit fullscreen mode

TRANSACTION CONTROLLER

package com.bankapp.controller;

import com.bankapp.model.Account;
import com.bankapp.model.Transaction;
import com.bankapp.service.AccountService;
import com.bankapp.service.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/transactions")
public class TransactionController {

    private final TransactionService transactionService;
    private final AccountService accountService;

    @Autowired
    public TransactionController(TransactionService transactionService, AccountService accountService) {
        this.transactionService = transactionService;
        this.accountService = accountService;
    }

    @PostMapping("/deposit")
    public ResponseEntity<?> deposit(@RequestParam String toAccountNumber,
                                     @RequestParam double amount,
                                     @RequestParam String description) {
        return accountService.getByAccountNumber(toAccountNumber).map(account -> {
            Transaction txn = transactionService.deposit(account, amount, description);
            return ResponseEntity.ok(txn);
        }).orElse(ResponseEntity.badRequest().body("To Account not found"));
    }

    @PostMapping("/withdraw")
    public ResponseEntity<?> withdraw(@RequestParam String fromAccountNumber,
                                      @RequestParam double amount,
                                      @RequestParam String description) {
        return accountService.getByAccountNumber(fromAccountNumber).map(account -> {
            try {
                Transaction txn = transactionService.withdraw(account, amount, description);
                return ResponseEntity.ok(txn);
            } catch (IllegalArgumentException e) {
                return ResponseEntity.badRequest().body(e.getMessage());
            }
        }).orElse(ResponseEntity.badRequest().body("From Account not found"));
    }

    @PostMapping("/transfer")
    public ResponseEntity<?> transfer(@RequestParam String fromAccountNumber,
                                      @RequestParam String toAccountNumber,
                                      @RequestParam double amount,
                                      @RequestParam String description) {
        var from = accountService.getByAccountNumber(fromAccountNumber);
        var to = accountService.getByAccountNumber(toAccountNumber);

        if (from.isEmpty() || to.isEmpty()) {
            return ResponseEntity.badRequest().body("Invalid account(s)");
        }

        try {
            Transaction txn = transactionService.transfer(from.get(), to.get(), amount, description);
            return ResponseEntity.ok(txn);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }

    @GetMapping("/{accountNumber}")
    public ResponseEntity<?> getTransactions(@PathVariable String accountNumber) {
        return accountService.getByAccountNumber(accountNumber).map(account -> {
            List<Transaction> txns = transactionService.getTransactionsByAccount(account);
            return ResponseEntity.ok(txns);
        }).orElse(ResponseEntity.notFound().build());
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)