<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chidinma Oham</title>
    <description>The latest articles on DEV Community by Chidinma Oham (@chi25).</description>
    <link>https://dev.to/chi25</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3384691%2F2f2e6106-81ff-42b3-b30f-804bb0b210b7.jpg</url>
      <title>DEV Community: Chidinma Oham</title>
      <link>https://dev.to/chi25</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chi25"/>
    <language>en</language>
    <item>
      <title>Documenting JavaScript the Easy Way</title>
      <dc:creator>Chidinma Oham</dc:creator>
      <pubDate>Thu, 14 May 2026 14:15:56 +0000</pubDate>
      <link>https://dev.to/chi25/documenting-javascript-the-easy-way-524m</link>
      <guid>https://dev.to/chi25/documenting-javascript-the-easy-way-524m</guid>
      <description>&lt;p&gt;Ever been in a situation where you’re under a lot of pressure or chasing a deadline, so you thought, “I probably shouldn’t bother with thoughtful variable names or documentation right now. I’ll clean it up later.”&lt;/p&gt;

&lt;p&gt;But you never did. One compromise becomes two, then five, and before you know it, your codebase is full of little shortcuts, vague functions and variable names that make no sense.&lt;/p&gt;

&lt;p&gt;When you come back months later, even you can’t quite remember what that function was supposed to do. Everything is a mess, and you end up spending hours figuring out what your code even does.&lt;/p&gt;

&lt;p&gt;We’ve all been there.&lt;/p&gt;

&lt;p&gt;Some months ago, I thought I found a solution with JSDoc and spent some time reading a bit about it. I found it really interesting, and so naturally I decided to use JSDoc comments in all of my next projects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JSDoc is an API documentation generator for JavaScript. It is based on a series of tags (words preceded by the @ symbol) that are used before each function or module and describe the different characteristics of the code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But then I hit a wall. Writing the comments was one thing; actually seeing them as useful documentation was another. The standard JSDoc output felt too impractical, and I found myself rarely checking the generated docs even on my own projects.&lt;/p&gt;

&lt;p&gt;So I built JSDocViewer. It’s a small CLI tool that can be used to generate interactive documentation for your JavaScript projects using the JSDoc format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building JSDocViewer
&lt;/h2&gt;

&lt;p&gt;JSDocViewer started as a simple Node.js CLI. The idea was to take a folder of .js files with proper JSDoc comments, parse them with the JSDoc tool and then serve the generated documentation in a local browser.&lt;/p&gt;

&lt;p&gt;But I wanted a bit more than the standard output. I wanted something that could really be navigated through. Grouped and styled output. A clean single-page viewer. Easy integration into any project folder.&lt;/p&gt;

&lt;p&gt;It’s open-source and available on my GitHub. The README has all the technical details.&lt;/p&gt;

&lt;p&gt;But here’s some value I found while building that I want to share:&lt;/p&gt;

&lt;p&gt;Consistently adding JSDoc comments helps you communicate about your code. Even the task of having to figure out the comment you will write on top of one method will prompt you to think and to clarify your method’s purpose&lt;/p&gt;

&lt;h2&gt;
  
  
  But Isn’t JSDoc a Bit… Old School?
&lt;/h2&gt;

&lt;p&gt;Sure, there are flashier tools. But that’s kind of the point. JSDoc is low overhead. No need for new frameworks or build tools. Just write good comments and run the generator.&lt;/p&gt;

&lt;p&gt;Want something fancier? Pair JSDoc with themes like DocStrap or use tools like TypeDoc for TypeScript.&lt;/p&gt;

&lt;p&gt;But even plain JSDoc can transform any project’s readability especially for team projects, open-source contributions or onboarding new engineers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building JSDocViewer was a fun and clearly rewarding experience that showed the importance of clear communication in coding. As I developed the tool, I realized how effective JSDoc comments can be in making code more accessible and understandable.&lt;/p&gt;

&lt;p&gt;So, if your codebase is filled with confusing functions and unclear outputs, try adding just a few JSDoc comments. Then run a tool like mine and see what your code actually looks like to someone new.&lt;/p&gt;

&lt;p&gt;Your future self will thank you. And so will your teammates.&lt;/p&gt;

&lt;p&gt;Want to try JSDocViewer? Check it out &lt;a href="https://github.com/TechEnginHER/JSDocViewer" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;. Got questions, ideas or comments on this? I’d love to hear from you.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>learning</category>
    </item>
    <item>
      <title>5 Hidden DevTools Features You Wish You Knew Sooner</title>
      <dc:creator>Chidinma Oham</dc:creator>
      <pubDate>Thu, 14 May 2026 14:11:30 +0000</pubDate>
      <link>https://dev.to/chi25/5-hidden-devtools-features-you-wish-you-knew-sooner-2a1a</link>
      <guid>https://dev.to/chi25/5-hidden-devtools-features-you-wish-you-knew-sooner-2a1a</guid>
      <description>&lt;p&gt;Let me guess. You mostly use your browser DevTools to inspect elements, tweak a bit of CSS and maybe log a few things in the console. Same here. At least until I realized DevTools is hiding some ridiculously powerful tools in plain sight.&lt;/p&gt;

&lt;p&gt;These features are not new but they’re easy to miss. And once I started using them, my debugging workflow got way smoother and faster. If you spend a lot of time in your browser while building or testing stuff, these might save you hours too.&lt;/p&gt;

&lt;p&gt;Let’s talk about a few of my favorites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Toggle Element State&lt;/strong&gt;&lt;br&gt;
The toggle element state lets you simulate CSS states like hover, focus or active directly in the DevTools panel. It’s perfect for fine-tuning interactive elements that only show styles after a user does something. Say goodbye to chasing hover styles before they disappear.&lt;/p&gt;

&lt;p&gt;Example Use Case: You’re styling a dropdown menu that only appears on hover. Toggle hover, inspect the dropdown and apply your styles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpdv2dx8s1vf436sm5k2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpdv2dx8s1vf436sm5k2.png" alt="Toggle Element State Screenshot" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use it:&lt;br&gt;
Dev Tools → Inspect Element → Toggle Element State → Select Desired State&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. CSS Overview&lt;/strong&gt;&lt;br&gt;
Right click an element → Toggle Element State → Select desired state&lt;/p&gt;

&lt;p&gt;CSS Overview scans your page and gives you a breakdown of your CSS including color usage, font stacks, unused declarations and even contrast issues. It’s a great way to audit the style without additional plugins. You get a report of how consistent your design system is.&lt;/p&gt;

&lt;p&gt;Example Use case: You inherited a codebase and want to see which fonts and color scheme are being used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2068efjerbsbjcsyyyt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2068efjerbsbjcsyyyt.png" alt="CSS Overview Screenshot" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use it:&lt;br&gt;
DevTools → Control + Shift + P → Search “Show CSS Overview”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Network Throttling&lt;/strong&gt;&lt;br&gt;
Network Throttling simulates slow internet speeds like “Fast 3G” or “Offline” to test how your site performs under bad network conditions. If you regularly build with users in mind, this one is a real game-changer.&lt;/p&gt;

&lt;p&gt;Example Use case: You’re working on a landing page with lots of images and media. Switch to “Slow 3G” and see what takes too long to load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0wupcj9do2kuh6gu5sw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0wupcj9do2kuh6gu5sw.png" alt="Network Throttling Screenshot" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use it:&lt;br&gt;
DevTools → Network Tab→ Throttle dropdown→ Pick connection profile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Sensors Panel&lt;/strong&gt;&lt;br&gt;
The sensors panel is one of my favorites and a must-use for building anything with geolocation or device motion. The Sensors tool lets you simulate your device’s location, orientation and touch inputs.&lt;/p&gt;

&lt;p&gt;Example Use case: You’re building a food delivery app that shows nearby restaurants. Use Sensors to change your location and check how the UI responds in different cities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzh8mlpxnb9uv0rv1p2qa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzh8mlpxnb9uv0rv1p2qa.png" alt="Sensors Panel Screenshot" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use it:&lt;br&gt;
DevTools → More Tools→ Sensors Tab&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Live Expressions&lt;/strong&gt;&lt;br&gt;
Live Expressions display the value of any JavaScript expression in real time right there in the console. No more spamming console.log just to see if a variable is changing.&lt;/p&gt;

&lt;p&gt;Example Use Case: You want to watch the value of user.isLoggedIn change as users navigate around. Add it as a Live Expression.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgx8m0jm1vndy8dexxpha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgx8m0jm1vndy8dexxpha.png" alt="Live Expressions Screenshot" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use it:&lt;br&gt;
DevTools → Console Tab→ Click Eye Icon or ‘+’ → Type any JS expression&lt;br&gt;
The best developers are not just great at code. They know their tools like the back of their hand and that’s probably the gap between you and them. DevTools has so much more to offer than most of us use.&lt;/p&gt;

&lt;p&gt;If you already use any of these or have other favorites I missed, I’d love to hear about them.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>software</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Modern Authentication: Beyond JWT</title>
      <dc:creator>Chidinma Oham</dc:creator>
      <pubDate>Thu, 14 May 2026 13:17:23 +0000</pubDate>
      <link>https://dev.to/chi25/modern-authentication-beyond-jwt-46hf</link>
      <guid>https://dev.to/chi25/modern-authentication-beyond-jwt-46hf</guid>
      <description>&lt;p&gt;At some point in your developer journey, you were told to use JWTs for authentication. For some, it was from a YouTube tutorial. For others, a blog post or perhaps a senior member of your team. Either way, you pasted the code, got a token and logged in. All was well.&lt;/p&gt;

&lt;p&gt;But then, something broke.&lt;/p&gt;

&lt;p&gt;The problem is with the way we talk about authentication. We often reduce it to implementation details like "use JWT" or "store the token here" without asking the important questions. Why this token format? Why this flow? Why this storage method?&lt;/p&gt;

&lt;p&gt;The truth is JWT is just a format. A way to package data. It is not a full authentication system. It does not handle how tokens are issued. It does not protect against interception. And it definitely does not teach you how to secure your application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations of JWT as a One-Size-Fits-All Solution
&lt;/h2&gt;

&lt;p&gt;JWTs are great for certain use cases. They are self-contained, compact and easy to parse. They also come with risks that many developers might overlook or not fully understand.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;JWT (JSON Web Token)&lt;/strong&gt; is a way to represent claims between two parties. It's commonly used to authenticate users by embedding their info (like ID or role) in a signed token that can be passed between the frontend and backend.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A few problems show up in the real world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Long-lived tokens stored in localStorage&lt;/strong&gt; become a liability. If someone gets access to the browser storage, they own the token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of token revocation&lt;/strong&gt; means that once a JWT is issued, it is valid until it expires. If it gets stolen, the system has no built-in way to kill it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too much data in the token&lt;/strong&gt; makes it vulnerable to leaks. Some developers include sensitive user information in the payload, which gets base64 encoded — not encrypted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So yes, JWTs are useful. But they are not secure by default. And they are definitely not enough on their own.&lt;/p&gt;




&lt;h2&gt;
  
  
  What OAuth 2.1 Actually Solves
&lt;/h2&gt;

&lt;p&gt;OAuth 2.1 removes outdated flows, enforces better defaults and makes things safer for public clients like single-page apps and mobile apps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;OAuth (Open Authorization)&lt;/strong&gt; is a protocol that allows third-party applications to access user data without exposing their password.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some key changes worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The implicit flow is deprecated.&lt;/strong&gt; This is huge. In earlier versions, SPAs would request tokens directly from the authorization server without a code exchange step. That approach exposed tokens in the browser and skipped key validation steps.&lt;/li&gt;
&lt;li&gt;OAuth 2.1 enforces the use of &lt;strong&gt;authorization code flow with PKCE&lt;/strong&gt;, even for public clients. This adds a layer of protection during the token exchange.&lt;/li&gt;
&lt;li&gt;Refresh tokens for SPAs are now handled with more nuance, using &lt;strong&gt;rotating refresh tokens&lt;/strong&gt; and secure storage patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The shift is subtle but meaningful. It basically means: we know how developers actually build apps today. Let's secure it properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Exactly is PKCE
&lt;/h2&gt;

&lt;p&gt;PKCE is not hard to understand. It is essentially a way to prove that the app requesting the token is the same app that started the process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;PKCE (Proof Key for Code Exchange)&lt;/strong&gt; is a security extension to OAuth. It protects the authorization code flow, especially in public clients that can't securely store secrets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The app generates a random string called a &lt;strong&gt;code verifier&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It hashes this value and sends the &lt;strong&gt;code challenge&lt;/strong&gt; to the authorization server.&lt;/li&gt;
&lt;li&gt;Later, when the app tries to exchange the authorization code for a token, it must provide the original code verifier.&lt;/li&gt;
&lt;li&gt;The server checks if the hashed verifier matches the challenge sent earlier.&lt;/li&gt;
&lt;li&gt;This prevents attackers from intercepting the authorization code and using it. Without the original code verifier, the exchange fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;PKCE protects against a real and common threat. And it works without needing a client secret.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Authentication
&lt;/h2&gt;

&lt;p&gt;Authentication is not just about passing the test case. It is about withstanding the real-world messiness of browsers, devices, networks and users.&lt;/p&gt;

&lt;p&gt;Some things to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use short-lived access tokens and long-lived refresh tokens.&lt;/strong&gt; Always rotate refresh tokens on use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store tokens securely.&lt;/strong&gt; In web apps, avoid &lt;code&gt;localStorage&lt;/code&gt;. Use HTTP-only cookies with &lt;code&gt;SameSite&lt;/code&gt; and &lt;code&gt;Secure&lt;/code&gt; flags when possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adopt token revocation strategies.&lt;/strong&gt; Blacklists, rotation and introspection endpoints can help.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rely on trusted auth providers&lt;/strong&gt; unless you have a good reason to build your own. Auth0, Clerk, Okta and others have done the hard work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log and monitor.&lt;/strong&gt; Treat authentication failures and token activity as security events. Alert when something unusual happens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not just to authenticate users. The goal is to protect the system, the data and the people using it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Does Any Of This Matter?
&lt;/h2&gt;

&lt;p&gt;The rise of AI tools and frameworks has made authentication feel like a solved problem. Paste this, configure that and it works.&lt;/p&gt;

&lt;p&gt;Until it doesn't.&lt;/p&gt;

&lt;p&gt;Good authentication is not just about getting users in. It's about building trust, preventing abuse and laying the foundation for a system that can grow without security breaches.&lt;/p&gt;

&lt;p&gt;You don't need to become an OAuth expert. But you do need to care about the decisions being made on your behalf.&lt;/p&gt;

&lt;p&gt;JWTs, OAuth and PKCE. They all have the same goal with different approaches. But when used together — and correctly — they form the backbone of modern authentication systems that actually scale.&lt;/p&gt;

&lt;p&gt;The key is to approach auth like you approach any other part of software engineering. With clarity. With care. With context.&lt;/p&gt;

&lt;p&gt;If you're building an app that handles user data, it is your responsibility. Proper authentication should never be an afterthought. It is a core part of the user experience and system security.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>security</category>
      <category>architecture</category>
      <category>java</category>
    </item>
    <item>
      <title>Step-by-Step Guide to Java REST APIs with OpenAPI Integration</title>
      <dc:creator>Chidinma Oham</dc:creator>
      <pubDate>Thu, 14 May 2026 13:12:08 +0000</pubDate>
      <link>https://dev.to/chi25/step-by-step-guide-to-java-rest-apis-with-openapi-integration-2gd8</link>
      <guid>https://dev.to/chi25/step-by-step-guide-to-java-rest-apis-with-openapi-integration-2gd8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A great API is one which people use. Not necessarily the one with the best design or technology behind it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If a developer from half a world away can successfully use your API without losing their minds, you've done a great job. Good documentation makes that possible and that's where OpenAPI and Swagger come in. Their auto-generated, interactive and standardized documentation takes your API from meh to something everyone wants to build around all with a few lines of setup.&lt;/p&gt;

&lt;p&gt;In this guide, I'll show you how to build a Java REST API using Spring Boot and integrate OpenAPI for simple and clear documentation. In five steps, you'll learn some key concepts, pro tips and how to make your API developer-friendly.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Setting up the Project
&lt;/h2&gt;

&lt;p&gt;Let's start by creating a new project using Spring Boot and Gradle (Groovy) in IntelliJ IDEA. If you're starting from scratch, generate a Spring Boot project with these dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spring Web:&lt;/strong&gt; To build the REST API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring Data JPA:&lt;/strong&gt; For database interaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL Server Driver:&lt;/strong&gt; To connect with SQL Server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Springdoc OpenAPI:&lt;/strong&gt; For API documentation (more on this later)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once your project is set up, Gradle will handle dependencies automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Structuring the API
&lt;/h2&gt;

&lt;p&gt;A clean architecture helps keep things maintainable. We're going to use this three-layered structure for the API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository Layer&lt;/strong&gt; — Manages database interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Layer&lt;/strong&gt; — Contains business logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controller Layer&lt;/strong&gt; — Handles HTTP requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each part has a clear responsibility and makes the API easy to extend and debug.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Connecting to SQL Server
&lt;/h2&gt;

&lt;p&gt;A lot of people stumble here especially when starting out, so let's make sure SQL Server is properly configured. To do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable TCP/IP connections in SQL Server Configuration Manager.&lt;/li&gt;
&lt;li&gt;Verify the SQL Server Browser service is running.&lt;/li&gt;
&lt;li&gt;Configure your firewall to allow connections on port 1433.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To connect to the database (we're using SQL Server here), add your database credentials in the &lt;code&gt;application.properties&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.datasource.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;jdbc:sqlserver://localhost:1433;databaseName=mydb&lt;/span&gt;
&lt;span class="py"&gt;spring.datasource.username&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your_username&lt;/span&gt;
&lt;span class="py"&gt;spring.datasource.password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your_password&lt;/span&gt;
&lt;span class="py"&gt;spring.jpa.hibernate.ddl-auto&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring Boot handles the connection and Hibernate takes care of ORM (Object-Relational Mapping).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;PRO TIP:&lt;/strong&gt; Use environment variables for sensitive information.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Creating the REST API
&lt;/h2&gt;

&lt;p&gt;Now that our connection is set up, we'll build a simple API to manage a user entity. This example will show the flow from request to database using the structure we defined earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entity Class
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Getters and setters &lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Repository Interface
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Service Class
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getAllUsers&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Controller Class
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/users"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt; &lt;span class="n"&gt;userService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getUsers&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAllUsers&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="nf"&gt;addUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you can test the endpoints we created using Postman. The basic GET and POST operations for User should be functional now.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Integrating OpenAPI with Spring Boot
&lt;/h2&gt;

&lt;p&gt;Now let's document our API in a way that's clean, interactive and self-updating. This is where Springdoc OpenAPI comes in.&lt;/p&gt;

&lt;p&gt;If you haven't already, include this in your &lt;code&gt;build.gradle&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;PRO TIP:&lt;/strong&gt; Run &lt;code&gt;./gradlew build&lt;/code&gt; or refresh Gradle so the dependencies are installed before you proceed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, to access the Swagger UI, run your application and go to: &lt;code&gt;http://localhost:8080/swagger-ui/index.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You'll see the auto-generated UI that shows our endpoints, models and request/response formats.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;PRO TIP:&lt;/strong&gt; Add basic annotations and enhance your endpoints with simple descriptions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;We've just built a functional and well-structured Java REST API and made it developer-friendly with OpenAPI integration. The beauty of using Springdoc is that it doesn't require heavy setup and your documentation always stays in sync with your code.&lt;/p&gt;

&lt;p&gt;Clean code is good. Clean, documented code? Even better.&lt;/p&gt;

&lt;p&gt;If you're new to this or just needed a refresher, hope this helped. I'd love to hear from you — comment or reach out with questions. I reply!&lt;/p&gt;

</description>
      <category>java</category>
      <category>api</category>
      <category>programming</category>
      <category>software</category>
    </item>
    <item>
      <title>JDBC vs JPA: Which Should You Use?</title>
      <dc:creator>Chidinma Oham</dc:creator>
      <pubDate>Thu, 14 May 2026 12:53:33 +0000</pubDate>
      <link>https://dev.to/chi25/jdbc-vs-jpa-which-should-you-use-bjg</link>
      <guid>https://dev.to/chi25/jdbc-vs-jpa-which-should-you-use-bjg</guid>
      <description>&lt;p&gt;When it comes to database interaction in Java, a choice is usually made between Java Database Connectivity (JDBC) and Java Persistence API (JPA). Some people say JDBC is outdated and avoid it entirely while others never tried JPA and prefer to stick to what works for them.&lt;/p&gt;

&lt;p&gt;Both of these technologies have their individual strengths and weaknesses so let's talk about the differences between them, their performances, best practices and a real-world use case to help you decide which to use in your next project.&lt;/p&gt;

&lt;p&gt;JDBC is Java's direct way of communicating with a database. It's basically writing SQL manually but in your Java code. It's been around since forever and gives you a lot more control. JPA is an API specification that lets you interact with a database using objects. It's a more modern approach that works just as great and handles boring database stuff for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  JDBC vs JPA: Key Differences
&lt;/h2&gt;

&lt;h4&gt;
  
  
  JDBC
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Direct SQL Execution:&lt;/strong&gt; Writing and executing SQL queries directly in the code gives you a much higher level of control over database operations. This comes in real handy in performance-critical applications where literally every millisecond matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection Management:&lt;/strong&gt; Because JDBC allows us full control over database connections such as opening, closing and managing connection pools; resource usage and performance are optimized.&lt;/p&gt;

&lt;h4&gt;
  
  
  JPA
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Object-Relational Mapping (ORM):&lt;/strong&gt; JPA offers a higher-level abstraction by mapping Java objects to database tables. This reduces boilerplate code and allows us to pay more attention to business logic rather than database interactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic SQL Generation:&lt;/strong&gt; SQL queries are automatically generated which typically makes development a lot quicker. However, this abstraction can sometimes result in less optimized SQL queries compared to manually written SQL.&lt;/p&gt;




&lt;h2&gt;
  
  
  JDBC vs JPA: Performance
&lt;/h2&gt;

&lt;h4&gt;
  
  
  JDBC
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; JDBC offers highly optimized performance due to the direct control over SQL execution and connection management.&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Increased development time and error rates due to manual management of database interactions.&lt;/p&gt;
&lt;h4&gt;
  
  
  JPA
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Increased speed of development as a result of the automatic SQL generation.&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; The abstraction layer could lead to less optimized SQL queries. It also requires careful configuration to avoid performance issues.&lt;/p&gt;


&lt;h2&gt;
  
  
  JDBC vs JPA: Best Practices
&lt;/h2&gt;
&lt;h4&gt;
  
  
  JDBC
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Connection Pooling:&lt;/strong&gt; Implement connection pooling to manage database &lt;br&gt;
connections efficiently and reduce the cost of opening and closing connections.&lt;br&gt;
&lt;strong&gt;Batch Updates:&lt;/strong&gt; Use batch updates to execute multiple SQL statements in a single call to reduce the number of round trips to the database.&lt;br&gt;
&lt;strong&gt;Indexing:&lt;/strong&gt; Ensure that database tables are properly indexed to speed up query performance.&lt;/p&gt;
&lt;h4&gt;
  
  
  JPA
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Eager vs. Lazy Loading:&lt;/strong&gt; Use eager loading for data that is frequently accessed together and lazy loading for less frequently accessed data as a way to optimize performance.&lt;br&gt;
&lt;strong&gt;Caching:&lt;/strong&gt; Configure caching mechanisms provided by JPA implementations to reduce queries to the database and improve performance.&lt;br&gt;
&lt;strong&gt;Avoid N+1 Select Problem:&lt;/strong&gt; Use fetch joins or batch fetching to avoid the N+1 select problem which can significantly reduce performance.&lt;/p&gt;


&lt;h3&gt;
  
  
  JDBC vs JPA: How It Works
&lt;/h3&gt;

&lt;p&gt;Let's compare the two using a simple fictional application where we need to retrieve a user's details from the database. Using JDBC, we can directly write the SQL query to fetch the data. With JPA, we can simplify the process by reducing boilerplate code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JDBC:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;PreparedStatement&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
      &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT * FROM users WHERE id = ?"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JPA:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Entity&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick Decision Guide
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;If you prioritize control and high-performance (every millisecond counts), **JDBC&lt;/em&gt;* is for you.&lt;br&gt;
&lt;em&gt;If you're all about scalability with minimal database complexity, **JPA&lt;/em&gt;* is the way to go.&lt;br&gt;
*If you want the best of both worlds, you can mix both — JPA for most &lt;br&gt;
operations and JDBC as a fallback for more critical queries.&lt;/p&gt;




&lt;h2&gt;
  
  
  JDBC vs JPA: Hybrid Setup
&lt;/h2&gt;

&lt;p&gt;If we were building an e-commerce platform that could grow to handle thousands of transactions per second, JPA would be a good starting choice for its simplicity and maintainability. Managing operations would be straightforward using entity mappings and we would avoid writing repetitive SQL.&lt;/p&gt;

&lt;p&gt;As our platform grows, we may start having performance issues during sales maybe, when thousands of users place orders simultaneously. JPA's automatic query generation might not be optimized for handling large-scale bulk inserts.&lt;/p&gt;

&lt;p&gt;This is where our hybrid setup comes in. JPA would remain our primary choice for managing entities and day-to-day database operations but for the more demanding tasks we would use plain old JDBC.&lt;/p&gt;




&lt;h2&gt;
  
  
  JDBC vs JPA: Which Do You Use?
&lt;/h2&gt;

&lt;p&gt;The choice depends on the specific requirements of your project and personal preference of course. You could decide to go old school with JDBC and enjoy the control and performance benefits it offers. You could decide to stay on top of trends and enjoy the high-level abstraction JPA offers. As long as you're building stuff — stuff that matters, they're both just means to an end.&lt;/p&gt;

</description>
      <category>java</category>
      <category>software</category>
      <category>sql</category>
      <category>programming</category>
    </item>
    <item>
      <title>Build Your First Semantic Search with Sentence Transformers and ChromaDB</title>
      <dc:creator>Chidinma Oham</dc:creator>
      <pubDate>Thu, 14 May 2026 12:27:50 +0000</pubDate>
      <link>https://dev.to/chi25/build-your-first-semantic-search-with-sentence-transformers-and-chromadb-1chh</link>
      <guid>https://dev.to/chi25/build-your-first-semantic-search-with-sentence-transformers-and-chromadb-1chh</guid>
      <description>&lt;p&gt;I recently finished watching Game of Thrones (no comment on the final season) and as the final credits rolled, I wasn’t quite ready to leave that atmosphere behind. I loved the political scheming, the shifting loyalties and even the moral ambiguity of certain characters so I found myself wanting to read a book that captured that exact same vibe or maybe even listen to a playlist that matched the emotional texture of it all.&lt;/p&gt;

&lt;p&gt;Most search engines couldn't help me because they were built to recommend based on genre, popularity or what other people had clicked. It wouldn't understand feelings or moods or the subtle nuances of human emotion. I didn't want “another fantasy show”. I wanted something that felt emotionally adjacent to what I had just experienced.&lt;/p&gt;

&lt;p&gt;That idea stayed in my head for a while and eventually I decided to play around with transformers to see what I could do about it.&lt;/p&gt;

&lt;p&gt;The result was a semantic media recommendation engine using ChromaDB and Sentence Transformers that takes in a natural language input describing an emotion, vibe, concept or narrative situation, converts that input into embeddings then retrieves semantically similar media recommendations across books, films, poems and songs.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build that cross-media recommender using semantic search, understand the core concepts that power it and compare different architectures along with their benefits and trade offs. &lt;/p&gt;

&lt;h3&gt;
  
  
  What We Are Building
&lt;/h3&gt;

&lt;p&gt;At a high level, the system works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A dataset of media items is prepared.&lt;/li&gt;
&lt;li&gt;Each media item is converted into an embedding vector.&lt;/li&gt;
&lt;li&gt;Those embeddings are stored inside ChromaDB.&lt;/li&gt;
&lt;li&gt;A user enters a natural language query.&lt;/li&gt;
&lt;li&gt;The query is embedded using the same model.&lt;/li&gt;
&lt;li&gt;ChromaDB retrieves semantically similar media items.&lt;/li&gt;
&lt;li&gt;The system returns recommendations across multiple media types.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq9fqk2q4z3cjhs0ylb8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq9fqk2q4z3cjhs0ylb8.png" alt="system architecture flowchart" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An important thing to note here is that we are not matching keywords.&lt;/p&gt;

&lt;p&gt;The user does not need to type: “sad movie”, “grief song” or “political fantasy book”&lt;/p&gt;

&lt;p&gt;Instead, they can describe an emotion or situation naturally and the system retrieves results based on semantic similarity. For instance; "last day in a city before I relocate"&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tech Stack
&lt;/h3&gt;

&lt;p&gt;For this project, we'll be using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;ChromaDB&lt;/li&gt;
&lt;li&gt;Sentence Transformers (The all-MiniLM-L6-v2 embedding model)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is intentionally simple so we can appreciate the mechanics of semantic retrieval before adding APIs, interfaces or LLM integrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Embeddings
&lt;/h3&gt;

&lt;p&gt;Before writing any code, we need to understand embeddings properly.&lt;/p&gt;

&lt;p&gt;An embedding is a numerical representation of text. When we pass text into a transformer model, the model converts that text into a high-dimensional vector.&lt;/p&gt;

&lt;p&gt;Something like:&lt;/p&gt;

&lt;p&gt;[0.231, -0.884, 0.442, ...]&lt;/p&gt;

&lt;p&gt;The actual numbers are not important. What matters is spatial similarity. Text with similar meaning ends up mathematically close together in vector space.&lt;/p&gt;

&lt;p&gt;For example: &lt;/p&gt;

&lt;p&gt;“grief after death”&lt;br&gt;
“mourning someone”&lt;br&gt;
“coping with loss”&lt;/p&gt;

&lt;p&gt;may all exist near each other inside that space. Meanwhile:&lt;/p&gt;

&lt;p&gt;“summer beach party”&lt;br&gt;
“high energy dance music”&lt;/p&gt;

&lt;p&gt;would likely exist far away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kggr7cp0lg6ftet5v2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kggr7cp0lg6ftet5v2u.png" alt="3D embedding visualization on a white background showing how semantically similar concepts appear near one another in embedding space while emotionally unrelated concepts are positioned farther apart" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Setting Up the Project
&lt;/h3&gt;

&lt;p&gt;Create a new folder for the project and navigate into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir semantic-media-search
cd semantic-media-search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a virtual environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m venv venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;venv\Scripts\activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once activated, install the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install chromadb sentence-transformers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what each package is responsible for:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sentence-transformers&lt;/code&gt; handles text embeddings using transformer models.&lt;br&gt;
&lt;code&gt;chromadb&lt;/code&gt; stores and retrieves embeddings using vector similarity search.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Project Structure
&lt;/h3&gt;

&lt;p&gt;Create the following project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;semantic-media-search/
│
├── data/
│   └── media.json
│
├── chroma_db/
│
├── embed.py
├── search.py
└── venv/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Structuring The Dataset
&lt;/h3&gt;

&lt;p&gt;Since semantic search depends heavily on contextual meaning, the quality of the dataset matters a lot.&lt;/p&gt;

&lt;p&gt;For this prototype, we'd be using a manually curated json dataset containing books, films, songs and poems. Each item contains a title, creator, themes, mood, description and media type. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;media.json&lt;/code&gt; file inside the &lt;code&gt;data&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "id": "1",
    "title": "Purple Hibiscus",
    "type": "book",
    "creator": "Chimamanda Ngozi Adichie",
    "themes": ["family", "religion", "silence"],
    "mood": ["melancholic", "tense"],
    "description": "A coming-of-age story exploring control, silence and political unrest."
  },
  {
    "id": "2",
    "title": "Moonlight",
    "type": "film",
    "creator": "Barry Jenkins",
    "themes": ["identity", "loneliness", "masculinity"],
    "mood": ["introspective", "emotional"],
    "description": "A deeply emotional film about identity, vulnerability and human connection."
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing you'll quickly realize while building this project is that embeddings become much better when the descriptions are emotionally descriptive instead of mechanically factual. For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"description": "A fantasy film released in 2012"&lt;/code&gt; does not carry much semantic value. Meanwhile:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"description": "A haunting story about grief, memory and emotional isolation"&lt;/code&gt; contains significantly richer contextual meaning for the embedding model to work with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Creating Embeddings
&lt;/h3&gt;

&lt;p&gt;Now we can begin generating embeddings from our media dataset.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;embed.py&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import chromadb
from sentence_transformers import SentenceTransformer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first load the embedding model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print("Downloading and loading model...")
model = SentenceTransformer('all-MiniLM-L6-v2')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time you run this, the model will be downloaded locally. Next, initialize ChromaDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chroma_client = chromadb.PersistentClient(path="./chroma_db")

collection = chroma_client.get_or_create_collection(
    name="media_recommendations"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using PersistentClient instead of an in-memory database because we want the embeddings stored permanently between runs.&lt;/p&gt;

&lt;p&gt;Now load the dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;with open('data/media.json', 'r') as file:
    media_items = json.load(file)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, we need to convert each media item into embedding-friendly text.&lt;/p&gt;

&lt;p&gt;This is an important step.&lt;/p&gt;

&lt;p&gt;We are not embedding only the description field. Instead, we combine the title, themes, mood, creator and description into a single contextual string. That gives the transformer more semantic information to work with.&lt;/p&gt;

&lt;p&gt;Inside a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for item in media_items:

    embedding_text = f"""
    Title: {item['title']}
    Type: {item['type']}
    Creator: {item['creator']}
    Themes: {", ".join(item['themes'])}
    Mood: {", ".join(item['mood'])}
    Description: {item['description']}
    """

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now generate the embedding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;embedding = model.encode(embedding_text).tolist()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.tolist()&lt;/code&gt; conversion is necessary because ChromaDB expects standard Python lists instead of NumPy arrays.&lt;/p&gt;

&lt;p&gt;Next, store everything inside ChromaDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;collection.add(
    ids=[item["id"]],
    embeddings=[embedding],
    documents=[embedding_text],
    metadatas=[{
        "title": item["title"],
        "type": item["type"],
        "creator": item["creator"]
    }]
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, print a success message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print("Success! Embeddings stored in ChromaDB.")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python embed.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything works correctly, your embeddings will now be stored inside the &lt;code&gt;chroma_db&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;At this point, the system understands your media semantically. We now need a way to retrieve similar results from natural language input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Building The Search Layer
&lt;/h3&gt;

&lt;p&gt;Inside the search.py file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import chromadb
from sentence_transformers import SentenceTransformer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load the same embedding model again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model = SentenceTransformer('all-MiniLM-L6-v2')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part is extremely important. The same embedding model used during ingestion must also be used during retrieval. Otherwise, the vectors would exist in different semantic spaces and similarity search would break.&lt;/p&gt;

&lt;p&gt;Now reconnect to ChromaDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chroma_client = chromadb.PersistentClient(path="./chroma_db")

collection = chroma_client.get_collection(
    name="media_recommendations"
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take user input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query = input(
    "What kind of vibe or story are you looking for?\n&amp;gt; "
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Convert the query into an embedding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query_embedding = model.encode(query).tolist()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now comes the retrieval step.&lt;/p&gt;

&lt;p&gt;Instead of retrieving random recommendations across all media, we will intentionally retrieve one recommendation for each media type: a book, a film, a song and a poem.&lt;/p&gt;

&lt;p&gt;Define the media types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;media_types = ["book", "film", "song", "poem"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now loop through them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for media in media_types:

    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=1,
        where={"type": media}
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;where&lt;/code&gt; filter ensures we retrieve recommendations within each category separately. Without this filter, the system might return four books or four songs depending on vector similarity.&lt;/p&gt;

&lt;p&gt;Now handle the results safely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if len(results["metadatas"][0]) &amp;gt; 0:

    metadata = results["metadatas"][0][0]

    print("-------------------------")
    print(f"Type: {metadata['type'].upper()}")
    print(f"Title: {metadata['title']}")
    print(f"Creator: {metadata['creator']}")

else:

    print("-------------------------")
    print(f"Type: {media.upper()}")
    print("Result: Nothing found.")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python search.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then try prompts like:&lt;/p&gt;

&lt;p&gt;*grief after losing someone&lt;br&gt;
*feeling like a million bucks&lt;br&gt;
*last day in a city before relocating&lt;br&gt;
*the feeling of growing apart from your childhood&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture Comparisons and Trade-Offs
&lt;/h3&gt;

&lt;p&gt;The stack for this prototype is entirely local. We used a local instance of ChromaDB and a lightweight open-source embedding model. When you are building a semantic search engine, you generally have a few architectural routes each with its own headaches and perks.&lt;/p&gt;

&lt;h5&gt;
  
  
  Route 1: The Fully Local Stack (Our approach)
&lt;/h5&gt;

&lt;p&gt;We ran the database on our machine and generated embeddings using our own CPU. It was completely free. No API key or internet connection needed (after initial download) and nobody else has access to our data. However, this approach could be heavy on your local resources. The all-MiniLM-L6-v2 model is tiny but if you scale up to millions of rows or use a larger, more nuanced model your machine might struggle.&lt;/p&gt;

&lt;h5&gt;
  
  
  Route 2: The Managed API Stack
&lt;/h5&gt;

&lt;p&gt;You use OpenAI or Cohere for embeddings and a managed vector database like Pinecone or Weaviate Cloud. These services handle the heavy math, meaning your app runs fast on any device and scales effortlessly. But you pay per token and per database hour. Plus, you are completely dependent on external services staying online.&lt;/p&gt;

&lt;h5&gt;
  
  
  Route 3: The Hybrid Stack
&lt;/h5&gt;

&lt;p&gt;You might keep the database local or self-hosted but use an external API for the embeddings. This would allow you control your data storage while outsourcing the intense compute required for generating embeddings. You still have API dependency and moving large chunks of vector data back and forth over a network can create latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;This same architecture powers a wide range of real-world applications, from enterprise recommendation systems to customer support AI, semantic document search and any other contextual retrieval systems.&lt;/p&gt;

&lt;p&gt;When I was satisfied with the results in the terminal, I wrapped it in FastAPI and deployed to a web application with the help of Codex. I tested it out using a prompt of political scheming, shifting loyalties and characters making highly questionable moral choices. It handed me The Traitor Baru Cormorant by Seth Dickinson. 10/10 no notes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>database</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
