A production-ready social network backend built with Spring Boot, PostgreSQL, and Neo4j. Demonstrates polyglot persistence β using the right database for the right job.
This is a portfolio project that simulates the backend of a social network like Twitter or Instagram. It handles:
- User profiles (stored in PostgreSQL)
- Follow / Unfollow relationships (stored in Neo4j graph database)
- Friend-of-friend suggestions (powered by graph traversal)
The key insight: SQL databases slow down on "who follows who" queries as the network grows. A graph database (Neo4j) handles these relationships 1000x faster because it is built for connected data.
βββββββββββββββββββ
β Your Browser β
β or Terminal β
ββββββββββ¬βββββββββ
β HTTP/REST
ββββββββββΌβββββββββ
β Spring Boot β β Java backend (this code)
β ββ REST API β (Controllers)
β ββ Services β (Business logic)
β ββ JPA (SQL) β (User profiles)
β ββ Neo4jClient β (Follow graph)
ββββββββββ¬βββββββββ
ββββββ΄βββββ
βΌ βΌ
ββββββββββ ββββββββββββ
βPostgreSQLβ β Neo4j β
β(Alwaysdata)β β(AuraDB) β
βUsers, β βFollows, β
βProfiles β βSuggestionsβ
ββββββββββ ββββββββββββ
| Technology | Purpose | Why we use it |
|---|---|---|
| Java 25 | Programming language | Modern, fast, industry standard |
| Spring Boot 4.1 | Backend framework | Auto-configuration, REST APIs, database connections |
| Spring Data JPA | PostgreSQL ORM | Maps Java objects to SQL tables automatically |
| PostgreSQL 16 | Relational database | Stores structured data: users, profiles, posts |
| Neo4j 5 | Graph database | Stores relationships: follows, friend-of-friend paths |
| Neo4jClient | Graph query runner | Direct Cypher queries for complex graph logic |
| Lombok | Boilerplate reducer | Auto-generates getters/setters/constructors |
| Gradle | Build tool | Compiles, tests, and runs the project |
Before you start, you need:
-
Java 25 installed
java -version # Should show: openjdk version "25" or higher -
Gradle (comes with the project via wrapper)
-
A PostgreSQL database β You can use:
- Alwaysdata (free, cloud-hosted) β Recommended
- Local PostgreSQL
- Any other PostgreSQL provider
-
A Neo4j AuraDB account β Free forever tier:
- Go to https://neo4j.com/cloud/aura/
- Sign up and create an instance
- Save your Connection URI and Password
git clone https://github.com/Keshav2136/nearby-java-springboot.git
cd nearby-java-springboot# Copy the example config
cp src/main/resources/application.properties.example src/main/resources/application.propertiesOpen src/main/resources/application.properties and fill in your real credentials:
server.port=8081
# PostgreSQL (Alwaysdata or your provider)
spring.datasource.url=jdbc:postgresql://YOUR_HOST:5432/YOUR_DATABASE?sslmode=require
spring.datasource.username=YOUR_USERNAME
spring.datasource.password=YOUR_PASSWORD
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# Neo4j AuraDB
spring.neo4j.uri=neo4j+s://YOUR_INSTANCE.databases.neo4j.io
spring.neo4j.authentication.username=YOUR_USERNAME
spring.neo4j.authentication.password=YOUR_PASSWORDNote: The
application.propertiesfile is in.gitignoreso your passwords will never be committed.
# Compile and start the server
./gradlew bootRunWait for this line in the terminal:
Started NearbyApplication in X.XX seconds
Your API is live at: http://localhost:8081
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Check if API is running |
| Method | Endpoint | Description |
|---|---|---|
POST |
/social/users |
Create a new user |
GET |
/users |
List all users |
GET |
/users/{id} |
Get user by ID |
| Method | Endpoint | Description |
|---|---|---|
POST |
/social/follow?followerId={a}&followingId={b} |
User A follows User B |
POST |
/social/unfollow?followerId={a}&followingId={b} |
User A unfollows User B |
GET |
/social/{userId}/following |
Who does this user follow? |
GET |
/social/{userId}/followers |
Who follows this user? |
GET |
/social/{userId}/suggestions |
Friend-of-friend suggestions |
Open a new terminal while the server is running:
curl -X POST http://localhost:8081/social/users \
-H "Content-Type: application/json" \
-d '{"username":"alice","email":"alice@test.com"}'
# Note the "id" in the response (e.g., 1)
curl -X POST http://localhost:8081/social/users \
-H "Content-Type: application/json" \
-d '{"username":"bob","email":"bob@test.com"}'
# Note the "id" (e.g., 2)
curl -X POST http://localhost:8081/social/users \
-H "Content-Type: application/json" \
-d '{"username":"charlie","email":"charlie@test.com"}'
# Note the "id" (e.g., 3)Replace 1, 2, 3 with the actual IDs you got above:
# Alice follows Bob
curl -X POST "http://localhost:8081/social/follow?followerId=1&followingId=2"
# Bob follows Charlie
curl -X POST "http://localhost:8081/social/follow?followerId=2&followingId=3"
# Alice follows Charlie
curl -X POST "http://localhost:8081/social/follow?followerId=1&followingId=3"# Who does Alice follow?
curl http://localhost:8081/social/1/following
# Who follows Bob?
curl http://localhost:8081/social/2/followers
# Suggestions for Alice
# (Returns people that Bob follows, but Alice doesn't)
curl http://localhost:8081/social/1/suggestionsnearby-java-springboot/
βββ src/main/java/com/keshav/nearby/
β βββ NearbyApplication.java # Entry point
β βββ controller/
β β βββ UserController.java # User CRUD endpoints
β β βββ SocialController.java # Follow/suggestion endpoints
β βββ entity/
β β βββ User.java # PostgreSQL entity (JPA)
β βββ model/
β β βββ UserNode.java # Neo4j node (Graph)
β βββ repository/
β β βββ UserRepository.java # JPA repository (SQL)
β βββ service/
β βββ SocialService.java # Business logic + dual writes
βββ src/main/resources/
β βββ application.properties.example # Template (safe for Git)
β βββ application.properties # Real config (ignored by Git)
βββ build.gradle # Dependencies
βββ README.md # This file
When you call POST /social/users:
- PostgreSQL: User row is inserted into the
userstable - Neo4j: A
(:User)node is created with the same ID and username - Both operations are wrapped in
@Transactionalβ if one fails, both roll back
When you call POST /social/follow:
- Neo4j runs this Cypher query:
MATCH (a:User {userId: 1}), (b:User {userId: 2}) CREATE (a)-[:FOLLOWS]->(b)
- A relationship is created instantly β no slow SQL joins needed
When you call GET /social/1/suggestions:
- Neo4j traverses the graph:
MATCH (u:User {userId: 1})-[:FOLLOWS]->(friend)-[:FOLLOWS]->(fof) WHERE NOT (u)-[:FOLLOWS]->(fof) RETURN fof
- Returns people your friends follow, but you don't β the classic "People You May Know" algorithm
# Find and kill the process, or change the port in application.properties:
server.port=8082Another app is using the port. Either stop it or change server.port.
- Check your
application.propertiescredentials - Verify PostgreSQL allows remote connections (Alwaysdata does by default)
- Verify Neo4j AuraDB instance is Active (not paused)
This project now uses Neo4jClient (raw Cypher) instead of Spring Data Neo4j repositories to avoid version compatibility issues.
- Polyglot Persistence: Using PostgreSQL for structured data and Neo4j for graph data
- Spring Data JPA: Entity mapping, repositories, and database transactions
- Cypher Query Language: How to traverse graphs efficiently
- Neo4jClient: Direct Cypher execution when repositories are too abstract
- Dual-Write Transactions: Keeping two databases in sync with
@Transactional - Cloud Database Hosting: Using managed services (Alwaysdata + AuraDB) for zero-infrastructure development
Portfolio Source License 1.0 β Source available for learning and non-commercial use. Commercial use requires written permission.
Built by Keshav as a portfolio project to demonstrate backend architecture and polyglot database design.
"The right database for the right problem."