Lost & Found Portal — Full‑Stack Java (Backend:
Spring Boot, Frontend: React, DB: H2/MySQL)
This document contains a complete starter project: backend (Spring Boot + JPA), a simple React
frontend, instructions to run locally, and notes about switching to MySQL or deploying.
Project overview
• Backend: Spring Boot REST API exposing CRUD for Item (lost/found items).
• Frontend: React single-page app that lists items and lets users add new ones.
• DB: H2 in-memory for quick local testing (can be swapped to MySQL with 2 config changes).
File tree (suggested)
lost-and-found-backend/
├─ [Link]
└─ src/main/java/com/example/lostandfound/
├─ [Link]
├─ controller/[Link]
├─ model/[Link]
├─ repository/[Link]
└─ service/[Link]
└─ src/main/resources/[Link]
lost-and-found-frontend/
├─ [Link]
└─ src/
├─ [Link]
├─ [Link]
├─ components/[Link]
└─ components/[Link]
Backend — Maven ([Link])
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="[Link]
xmlns:xsi="[Link]
xsi:schemaLocation="[Link]
[Link]
<modelVersion>4.0.0</modelVersion>
1
<groupId>[Link]</groupId>
<artifactId>lost-and-found-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- If you choose MySQL later: uncomment
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
-->
</dependencies>
<properties>
<[Link]>17</[Link]>
</properties>
<build>
<plugins>
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Notes: use Java 17 (LTS). Upgrade versions if needed.
2
Backend — [Link] (src/main/resources/
[Link])
[Link]=8080
[Link]=jdbc:h2:mem:lostfounddb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
[Link]=sa
[Link]=
[Link]-class-name=[Link]
[Link]-platform=[Link].H2Dialect
[Link]=true
[Link]=/h2-console
[Link]-auto=update
# Allow CORS for local frontend (adjust origin for production)
# (CORS will also be enabled programmatically in controller)
To switch to MySQL, replace [Link] + username/password and add MySQL
connector dependency.
Backend — Java sources
[Link]
package [Link];
import [Link];
import [Link];
@SpringBootApplication
public class LostAndFoundApplication {
public static void main(String[] args) {
[Link]([Link], args);
}
}
model/[Link]
package [Link];
import [Link].*;
import [Link];
@Entity
@Table(name = "items")
public class Item {
@Id
3
@GeneratedValue(strategy = [Link])
private Long id;
private String title;
@Column(length = 2000)
private String description;
private String location;
private String contact;
private String status; // "LOST" or "FOUND"
private String imageUrl;
private LocalDateTime createdAt;
public Item() { [Link] = [Link](); }
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { [Link] = id; }
public String getTitle() { return title; }
public void setTitle(String title) { [Link] = title; }
public String getDescription() { return description; }
public void setDescription(String description) { [Link] =
description; }
public String getLocation() { return location; }
public void setLocation(String location) { [Link] = location; }
public String getContact() { return contact; }
public void setContact(String contact) { [Link] = contact; }
public String getStatus() { return status; }
public void setStatus(String status) { [Link] = status; }
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { [Link] = imageUrl; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { [Link] =
createdAt; }
}
repository/[Link]
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
List<Item> findByStatusOrderByCreatedAtDesc(String status);
}
4
service/[Link]
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Service
public class ItemService {
private final ItemRepository repo;
public ItemService(ItemRepository repo) { [Link] = repo; }
public Item save(Item item) { return [Link](item); }
public List<Item> findAll() { return [Link](); }
public List<Item> findByStatus(String status) { return
[Link](status); }
public Optional<Item> findById(Long id) { return [Link](id); }
public void deleteById(Long id) { [Link](id); }
}
controller/[Link]
package [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
@RestController
@RequestMapping("/api/items")
@CrossOrigin(origins = "[Link] // allow React dev server
public class ItemController {
private final ItemService service;
public ItemController(ItemService service) { [Link] = service; }
@GetMapping
public List<Item> all(@RequestParam(required = false) String status) {
if (status == null) return [Link]();
return [Link]([Link]());
}
5
@GetMapping("/{id}")
public ResponseEntity<Item> get(@PathVariable Long id) {
return [Link](id)
.map(ResponseEntity::ok)
.orElse([Link]().build());
}
@PostMapping
public Item create(@RequestBody Item item) {
return [Link](item);
}
@PutMapping("/{id}")
public ResponseEntity<Item> update(@PathVariable Long id, @RequestBody
Item update) {
return [Link](id).map(existing -> {
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
Item saved = [Link](existing);
return [Link](saved);
}).orElse([Link]().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
[Link](id);
return [Link]().build();
}
}
Frontend — React (minimal)
The frontend assumes the backend is running on [Link] .
[Link]
{
"name": "lost-and-found-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0",
6
"react-scripts": "5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
}
}
src/[Link]
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const container = [Link]('root');
const root = createRoot(container);
[Link](<App />);
src/[Link]
import React, { useEffect, useState } from 'react';
import ItemList from './components/ItemList';
import ItemForm from './components/ItemForm';
const API = '[Link]
export default function App() {
const [items, setItems] = useState([]);
const fetchItems = async () => {
const res = await fetch(API);
const data = await [Link]();
setItems(data);
};
useEffect(() => { fetchItems(); }, []);
const addItem = async (item) => {
const res = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link](item)
});
if ([Link]) fetchItems();
};
const deleteItem = async (id) => {
const res = await fetch(`${API}/${id}`, { method: 'DELETE' });
if ([Link]) fetchItems();
7
};
return (
<div style={{ padding: 20, fontFamily: 'Arial' }}>
<h1>College Lost & Found</h1>
<ItemForm onAdd={addItem} />
<hr/>
<ItemList items={items} onDelete={deleteItem} />
</div>
);
}
src/components/[Link]
import React, { useState } from 'react';
export default function ItemForm({ onAdd }) {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [location, setLocation] = useState('');
const [contact, setContact] = useState('');
const [status, setStatus] = useState('LOST');
const submit = (e) => {
[Link]();
if (!title) return alert('Title required');
onAdd({ title, description, location, contact, status });
setTitle(''); setDescription(''); setLocation(''); setContact('');
setStatus('LOST');
};
return (
<form onSubmit={submit} style={{ marginBottom: 20 }}>
<div>
<input placeholder="Title" value={title}
onChange={e=>setTitle([Link])} />
</div>
<div>
<textarea placeholder="Description" value={description}
onChange={e=>setDescription([Link])} />
</div>
<div>
<input placeholder="Location" value={location}
onChange={e=>setLocation([Link])} />
</div>
<div>
<input placeholder="Contact (phone/email)" value={contact}
onChange={e=>setContact([Link])} />
</div>
<div>
8
<select value={status} onChange={e=>setStatus([Link])}>
<option value="LOST">Lost</option>
<option value="FOUND">Found</option>
</select>
</div>
<button type="submit">Add Item</button>
</form>
);
}
src/components/[Link]
import React from 'react';
export default function ItemList({ items, onDelete }) {
if (!items || [Link] === 0) return <p>No items yet.</p>;
return (
<div>
{[Link](it => (
<div key={[Link]} style={{ border: '1px solid #ddd', padding: 8,
marginBottom: 8 }}>
<strong>{[Link]}</strong> <em>({[Link]})</em>
<p>{[Link]}</p>
<p><small>Location: {[Link]} — Contact: {[Link]}</small></
p>
<button onClick={() => onDelete([Link])}>Delete</button>
</div>
))}
</div>
);
}
How to run locally
Backend
1. cd lost-and-found-backend
2. mvn clean package
3. mvn spring-boot:run (or java -jar target/*.jar )
4. Spring Boot runs on [Link] . H2 console at
[Link] (JDBC URL from [Link]).
Frontend
1. cd lost-and-found-frontend
2. npm install
3. npm start (starts at [Link] )
9
Now you can add items from the UI and they will be stored in H2.
Next steps / enhancements (ideas)
• Add authentication (college accounts) with Spring Security.
• Allow image upload (store files in S3 or server filesystem + serve via URL).
• Add search/filters, categories, and a matching engine to suggest found items matching lost item
descriptions.
• Switch H2 to MySQL/Postgres for persistence across restarts.
• Add paging and sorting in the API.
• Add email or SMS notifications when matches are found.
If you want, I can: - provide the full ZIP of both projects, - change the backend to use MySQL and show
migration steps, - add authentication with JWT, - improve frontend styling using Tailwind or Material UI,
- or convert the frontend to a Thymeleaf server-rendered UI instead.
Tell me which of the above you want next and I will continue.
10