DEV Community

Cover image for From "Student" API to Professional Grade: JWT Auth, Swagger, and Zod
Renato Silva
Renato Silva

Posted on • Edited on

From "Student" API to Professional Grade: JWT Auth, Swagger, and Zod

Building an API that works on your local machine is just the first step. But what separates a hobby project from a production-ready product? In today's post, I'll show how I transformed my feedback system into a robust, secure, and fully documented application.

πŸ” 1. The Digital "Key": Implementing JWT

Up until now, anyone who discovered my API URL could read every feedback ever submitted. In a real-world scenario, this is a critical privacy failure.

To solve this, I implemented JWT (JSON Web Token) using @fastify/jwt. The flow now works as follows:

  1. The admin logs in with secure credentials.
  2. The API generates a signed token.
  3. This token must be sent in the header of every request to protected routes.

The game-changer here was using a Fastify decorator to create an authenticate hook. Now, protecting a route is as simple as adding a single line of code.

πŸ“– 2. Pro Swagger: Documentation that actually works

Documentation isn't enough; it needs to be useful. I was already using Swagger, but the challenge was: How do I test protected routes without leaving the browser?

I configured securitySchemes in Swagger to support the Bearer Auth pattern. The result? An "Authorize" button with a lock icon appeared at the top of the page. Now, I can log in through the interface itself, paste the token, and test private endpoints directly. Pure productivity.

πŸ›‘οΈ 3. Environment Shielding with Zod

A common mistake is an application failing in production because someone forgot to set a variable in the .env file.

To prevent this, I used Zod to create an environment "validator." I built a schema that checks:

  • If JWT_SECRET exists and is secure.
  • If DATABASE_URL is correct.
  • If PORT is actually a number.

If any of these variables are missing or wrong, the application crashes immediately at startup and tells you exactly what's wrong. This Fail-fast approach is a senior-level concept that brings real peace of mind.

// Validating our environment variables with Zod
const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
  PORT: z.coerce.number().default(3000),
  JWT_SECRET: z.string().min(1, "JWT_SECRET is required"),
  DATABASE_URL: z.string().min(1, "DATABASE_URL is required"),
});

const _env = envSchema.safeParse(process.env);

if (_env.success === false) {
  console.error('❌ Invalid environment variables!', _env.error.format());
  process.exit(1); // Stop the app if config is wrong
}
Enter fullscreen mode Exit fullscreen mode

πŸ—οΈ 4. Refactoring and Clean Architecture

As the API grew, the main file (app.js) started getting cluttered. I applied the "Separation of Concerns" principle:

Routes: Specific files to define paths and schemas.

Controllers: The bridge between HTTP and business logic.

App: Only orchestration and plugin registration.

This organization allowed the project to breathe and made maintenance much easier.

πŸš€ Pro-tip: Handling Git and .env

During the process, I learned (the hard way!) that adding .env to .gitignore after it's already tracked doesn't work. I had to clear the Git cache (git rm --cached .env) to ensure my secrets weren't stored in the commit history. Pro-tip: security is never too much!

Conclusion

My feedback API is no longer just code that saves data; it's an ecosystem with Testing (Vitest), Security (JWT), Interactive Docs (Swagger), and Continuous Deployment (Render).

Link to the project

What tools are you using to secure your APIs? Let’s talk in the comments! πŸ‘‡

Top comments (0)