Backend Fullstack Development
- Before You Start
- How do API work?
- How This Connects
- Step 1
- Step 2
- Step 3
- Step 4
- Step 5
- Step 6
- Step 7
- Step 8
- Study This With AI
- What’s Next
- Further Questions
Before You Start
Look back at these lessons from week 4
- This lesson puts them together into one working feature, so skim any that feel unfamiliar
| Lesson | Idea | Where it shows up here |
|---|---|---|
| Spring intro | How a Spring project fits together | The whole lesson |
| POJO | A Java class that becomes a table | Step 2, the entity |
| JPA | Reading and writing without SQL | Step 3, the repository |
| API Controller | Turning HTTP into method calls | Step 5, the controller |
How do API work?
Five standard parts
- Every Spring feature in our backend has the same five parts, named after the feature
- The announcement package (
src/main/java/com/open/spring/mvc/announcement) is the smallest complete example, so this lesson copies it
| Part | Announcement class | What it does |
|---|---|---|
| Entity | AnnouncementEntity |
The data object; one object is one row in the table |
| JPA repository | AnnouncementJpaRepository |
Reads and writes the table; Spring writes the SQL from method names |
| Service | AnnouncementService |
The rules of your feature; the only class that uses the repository |
| REST controller | AnnouncementApiRestController |
Turns HTTP requests into service calls and results into responses |
| Table initializer | AnnouncementTableInitializer |
Creates the table if it is missing |
- The full guide is announcement-architecture.md in the Spring repo
Why do we use API’s
These are the two main reason behind why we use them in this class
- Stores Data that is independent of the computer/frontend (Security, Ease of Accesses)
- Enables lazier coding (the data used from the API can be used to auto-generate HTML)
Accessing API’s using fetch
- They are accessed by fetching a specific URL in the fetch() method
- This method allows us to send list of headers with information we want the API to know
- GET, DELETE, POST, PUT (methods used in the controller file)
- Body (sending json data)
- CORS
- Cookies
- Content Type
- The rest of them (still very important things to know)
How This Connects
Where this feature sits in OCS and in CS112
In OCS
- Spring holds the data and the rules, which is what this lesson builds
- Pages is the frontend students see, built from
ocs__classes - UI Builder lets you put your API on a Pages page without writing HTML, which is Step 8
- Your feature follows the same path: copy the five parts, rename them, then connect a page
In CS112
These come from the CS112 outline report
| CS112 topic | What you do in this lesson |
|---|---|
| V.B Java applets or Web | Connect a browser page to a Spring endpoint and retrieve project data (Steps 5 and 8) |
| III.D Fault-tolerance through exception and error handling | Read the status code each route returns and test the error cases (Step 7) |
| III.B.1 Text and binary files | Save data to a table with JPA; this is related but does not replace writing files yourself |
Step 1
File Management
- Create your features folder name in
src/main/java/com/open/spring/mvc(for me it is announcement)- Locate the announcement folder, copy and rename to what your feature name is
- Copy all five files and rename each one to your feature, keeping the ending:
AnnouncementEntity.javabecomesYourFeatureEntity.java, and the same forJpaRepository,Service,ApiRestControllerandTableInitializer- Make sure to match the class name with the file name, change any other appearances of the former files name
Step 2
Entity File
- Change AnnouncementEntity.java to your features name + Entity.java
- Rename each instance of the word Announcement to your feature’s name use ctrl+f
- The annotations above the class tell Spring this object is a row in a table, and name the table
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "Announcement")
@Table(name = "announcement")
public class AnnouncementEntity {
Step 2.1
Column Variables
- There are a few variables at the beginning of the class
- Keep ID, but change to what your feature needs on the other variables
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(unique=false)
//These variables
private String author;
private String title;
private String body;
private String timestamp;
private String tags;
Step 2.2
Constructor Method
- This is the method that creates the objects that store your data
- Create something similar to this but with your variables
- You are passing through data for each column
// Constructor with necessary fields
public AnnouncementEntity(String author, String title, String body, String tags) {
this.author = author;
this.title = title;
this.body = body;
this.tags = tags;
this.timestamp = LocalDateTime.now().format(formatter);
}
Step 2.3
Init Method + Initial Data
- The innit method is ran at the beginning of the backend
- The method is what populates your table with data
- I only have it returning the data from init
- Why have two methods?
- if you need to manipulate the array more you can
- other processes need to run before your for your API
- nicer to look at too, abstraction helps readability
// Static method to create initial data
public static List<AnnouncementEntity> createInitialData() {
List<AnnouncementEntity> announcements = new ArrayList<>();
// Create announcements with formatted timestamp
announcements.add(new AnnouncementEntity("Finn", "Test of innit", "Don't mind this message", "welcome"));
return announcements;
}
// Static method to initialize the data
public static List<AnnouncementEntity> init() {
return createInitialData();
}
Step 3
JPA Repository file
- JPA stands for Java Persistence API
- Name it your feature + JpaRepository.java
- This file helps with specif query methods of your database
- For example if you wanted to create method for getting posts only based on an author, this file creates them
Step 3.1
Copy + Paste
- Replace the names with your project and data type names
- Super simple just use the same method name formatting
- The compiler will make the methods for you
- This code segment is your whole file
package com.open.spring.mvc.announcement;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AnnouncementJpaRepository extends JpaRepository<AnnouncementEntity, Long> {
AnnouncementEntity findByAuthor(String author); // Method to find announcement by author
List<AnnouncementEntity> findByTags(String tags); // Example method to find announcements by tags
AnnouncementEntity findByTitle(String title); // Example method to find announcements by title
List<AnnouncementEntity> findAllByOrderByTimestampDesc();
}
Step 4
Service File
- Name it your feature + Service.java
- The service holds the rules of your feature: what happens when someone creates, reads, edits or deletes
- It is the only class that uses the repository, so the controller never talks to the database directly
@Servicelets Spring hand it the repository through the constructor
@Service
public class AnnouncementService {
private final AnnouncementJpaRepository repository;
public AnnouncementService(AnnouncementJpaRepository repository) {
this.repository = repository;
}
// Saves a new announcement stamped with the current time
public AnnouncementEntity create(String author, String title, String body, String tags) {
return repository.save(new AnnouncementEntity(author, title, body, tags));
}
// Replaces the body of the announcement with this title
public AnnouncementEntity editBody(String title, String body) {
AnnouncementEntity announcement = repository.findByTitle(title);
announcement.setBody(body);
repository.save(announcement);
return announcement;
}
}
Step 5
Controller File
- Use AnnouncementApiRestController.java, copy and rename all parts to your API’s name similar to the first step
- This file is how you will manipulate your data from outside the local system
- It only translates HTTP: each method calls the service and turns the result into a response
- This is done through fetching different URLs with data embedded in the HTTP request
- Each controller file should have a base path that is different from the rest of the other API’s
- Looking above the class you should see this
@RestController
@RequestMapping("/api/announcements")
Step 5.1
Basic Methods (do not copy use as example)
- All we are doing is just assigning certain methods to URLs, that will do CRUD processes
- CRUD stands for Create Read Update Delete, and you want each kind
- Create -> POST, Read -> GET, Update -> PUT, Delete -> DELETE is the usual mapping
- The announcement API uses POST for edit and delete (
/edit/{title},/delete/{title}), because clients already call those routes- Methods return an
AnnouncementRecord, a small record nested in the controller, instead of the entity, so the JSON stays the same even if the table changes- Use CHATGPT to help with creation
// Create Example
@PostMapping("/create")
public ResponseEntity<AnnouncementRecord> createAnnouncement(@RequestParam String author, @RequestParam String title,
@RequestParam String body, @RequestParam String tags) {
AnnouncementEntity saved = announcementService.create(author, title, body, tags);
return new ResponseEntity<>(AnnouncementRecord.of(saved), HttpStatus.CREATED);
}
// Read Example
@GetMapping
public ResponseEntity<List<AnnouncementRecord>> getAllAnnouncements() {
return new ResponseEntity<>(AnnouncementRecord.of(announcementService.findAll()), HttpStatus.OK);
}
// Update Example
@PostMapping("/edit/{title}")
public ResponseEntity<AnnouncementRecord> editAnnouncement(@PathVariable String title, @RequestBody String body) {
AnnouncementEntity edited = announcementService.editBody(title, body);
return new ResponseEntity<>(AnnouncementRecord.of(edited), HttpStatus.OK);
}
// Delete Example
@PostMapping("/delete/{title}")
public ResponseEntity<HttpStatus> deleteAnnouncement(@PathVariable String title) {
try {
announcementService.deleteByTitle(title);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Detailed Breakdown
What each part of the controller means and does
- Breaking down the update method
// What each part of the method does
// Response Entity is used instead because they can send extra data upon the request back
//
//Determines the Mapping POST GET PUT DELETE
@PostMapping("/edit/{title}")
public ResponseEntity<AnnouncementRecord> editAnnouncement(@PathVariable String title, @RequestBody String body)
// Data Type Returned Parameters needed for the method
{
// The service finds the announcement, sets the new body and saves it
AnnouncementEntity edited = announcementService.editBody(title, body);
// Returning the announcement as a record, and the HTTP status, whether it worked or not
return new ResponseEntity<>(AnnouncementRecord.of(edited), HttpStatus.OK);
}
Step 5.2
Custom methods using JPA
- Sometimes you want to create specific query methods for your
- Example of fetching by author and tags
- The methods findByAuthor(), and findByTags() are made by the JPA not me
- The controller reaches them through service methods with the same names
@GetMapping("/author/{author}")
public ResponseEntity<AnnouncementRecord> getAnnouncementByAuthor(@PathVariable String author) {
AnnouncementEntity announcement = announcementService.findByAuthor(author);
if (announcement != null) {
return new ResponseEntity<>(AnnouncementRecord.of(announcement), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@GetMapping("/tags/{tags}")
public ResponseEntity<List<AnnouncementRecord>> getAnnouncementsByTags(@PathVariable String tags) {
return new ResponseEntity<>(AnnouncementRecord.of(announcementService.findByTags(tags)), HttpStatus.OK);
}
Step 6
Table Initializer File
- Name it your feature + TableInitializer.java
- Our backend runs with
ddl-auto=none, so Spring does not create tables for new entities by itself- This file creates your table when the backend starts, only if it is missing, and never changes a table that exists
- Use one column per variable from Step 2.1
- Because the id uses
GenerationType.AUTO, Spring also needs a_seqtable that holds the next id
@Bean
public ApplicationRunner migrateAnnouncement() {
return args -> {
try {
jdbcTemplate.execute(
"CREATE TABLE IF NOT EXISTS announcement ("
+ "id bigint NOT NULL PRIMARY KEY, "
+ "author varchar(255), "
+ "body varchar(255), "
+ "tags varchar(255), "
+ "timestamp varchar(255), "
+ "title varchar(255))");
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS announcement_seq (next_val bigint)");
seedIdCounterIfEmpty();
} catch (DataAccessException e) {
logger.warn("Could not create the announcement tables: {}", e.getMessage());
}
};
}
- Copy
seedIdCounterIfEmpty()from the announcement file as it is; it adds the first row of the_seqtable
Step 7
Popcorn Hack: test the API
- The Spring repo has api/announcement.http, which calls every announcement route in order
- Install the REST Client extension in VS Code, open the file, and click Send Request above each request
- Put
baseUrl,adminUidandadminPasswordin the Git-ignored.vscode/settings.json, never in the.httpfile- Then copy it to
api/yourfeature.httpand change the routes to test your own API
Step 8
Popcorn Hack: show your API with UI Builder
- Sign in, open UI Builder, and start from the Flask API starter template
- In Data from your API, press Test connection to see live jokes on your page
- Change the link to
http://localhost:8585/api/announcementsand press Test connection again- It says the route needs sign-in: every announcement route does, and UI Builder never sends sign-in cookies
- So your feature needs one read-only route that returns only public fields, for example
GET /api/yourfeature/public- Add that route to your controller, then allow it in
SecurityConfig.javaabove the/api/**rule
.requestMatchers(HttpMethod.GET, "/api/yourfeature/public").permitAll()
- Point the block at your new route, press Test connection, and choose how to show it (cards, list, table or numbers)
- Press Code to read the HTML and the
ocs__classes behind your page - A link to your own computer only works while you edit, so use your deployed
https://address before you Publish
The Class announcements block is a different feature: it shows a course’s announcement channel from Messages, not this API
Study This With AI
Paste your own code into your AI tool, then check what it says against the code
- Never paste passwords, tokens or your
.envfile
Draw it
Draw a Mermaid sequence diagram of one GET request moving through my controller,
service, repository and database, and back to the browser. Use my class names.
Explain it
Explain why my controller calls a service instead of the repository. Use my
feature as the example, in under 100 words.
Quiz me
Ask me 5 questions, one at a time, on the five standard parts of a Spring feature.
Wait for each answer and tell me what I missed.
Break it
List 3 requests that would fail on my API and the status code each one returns.
I will test them in my .http file and tell you where you were wrong.
What’s Next
See the same ideas in Python
- The Microblog API lesson traces a real Flask feature and matches it to these five parts
Further Questions
Please use the comments below to suggest things to be added
- Come to office hours if further help is needed
- So we may add to this notebook