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)