<?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: Francisco Luna</title>
    <description>The latest articles on DEV Community by Francisco Luna (@franciscolunadev82).</description>
    <link>https://dev.to/franciscolunadev82</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%2F1137857%2Ffd2def6f-acba-4fe7-8703-dc908c716c8b.jpg</url>
      <title>DEV Community: Francisco Luna</title>
      <link>https://dev.to/franciscolunadev82</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/franciscolunadev82"/>
    <language>en</language>
    <item>
      <title>I built Envoy - Control Plane for Painless PostgreSQL Schema Migrations</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Mon, 23 Mar 2026 00:35:13 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/i-built-envoy-control-plane-for-painless-postgresql-schema-migrations-3l40</link>
      <guid>https://dev.to/franciscolunadev82/i-built-envoy-control-plane-for-painless-postgresql-schema-migrations-3l40</guid>
      <description>&lt;p&gt;If you have some years of experience as a FullStack Developer, Dbadmin or DevOps engineer, you already know managing database migrations across Staging, QA, and Production is a mess. You already know how this game work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We switch .env files manually is dangerous and error-prone&lt;/li&gt;
&lt;li&gt;Then forget GRANT permissions in pgAdmin causes production downtime&lt;/li&gt;
&lt;li&gt;Audit logs are non-existent, scattered in sql files, or hard to verify&lt;/li&gt;
&lt;li&gt;Multiple tools required for simple database operations&lt;/li&gt;
&lt;li&gt;We build custom scripts we forget how to use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how every Friday was like in a regulated environment from an actual registered startup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I get the development database credentials&lt;/li&gt;
&lt;li&gt;Then replace the environment variable in .env file&lt;/li&gt;
&lt;li&gt;Run migration command with the orm&lt;/li&gt;
&lt;li&gt;Verify migration was successful&lt;/li&gt;
&lt;li&gt;Try changes in development&lt;/li&gt;
&lt;li&gt;Realize I forgot to give permissions to new tables&lt;/li&gt;
&lt;li&gt;Go back to pgAdmin/psql to fix permissions&lt;/li&gt;
&lt;li&gt;Try again&lt;/li&gt;
&lt;li&gt;Repeat next week across all environments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introducing Envoy
&lt;/h2&gt;

&lt;p&gt;This month I've built &lt;a href="https://github.com/franciscoluna-28/envoy" rel="noopener noreferrer"&gt;Envoy&lt;/a&gt;. It is an open source control plane for your PostgreSQL database schema migrations built as a Golang 1.25.0 API, which is consumed by a React/Vite Frontend. It connects to your database using PGX and encrypts all your connection strings using AES-256. &lt;/p&gt;

&lt;p&gt;Envoy does not care if you wrote the SQL yourself or if it comes from your ORM. It allows you to visualize the GRANT permissions, log migrations and work by environments in case your infrastructure has multiple projects or you manage multiple clients. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to use Envoy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clone the repository and initialize it using Docker Compose&lt;/li&gt;
&lt;li&gt;Create an account and login&lt;/li&gt;
&lt;li&gt;Create a new project and set up your first environment&lt;/li&gt;
&lt;li&gt;Connect to your Postgres database as an admin or any user capable of modifying the Postgres schema (I advice you to experiment with the PostgreSQL database from the dockerized environment first)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Envoy
&lt;/h2&gt;

&lt;p&gt;I built Envoy because no ORM considered actual compliance requirements and multiple users when working in multiple databases. They asssume you're always running the database as &lt;code&gt;doadmin&lt;/code&gt; or &lt;code&gt;postgres&lt;/code&gt;, which is a lie in actual startups with compliance. &lt;/p&gt;

&lt;p&gt;Every deployment was full of anxiety and rushed SQL commands to see permissions, not because I was careless, but because I was managing 3 environments at the same time on my own; each one with different users and needs. Also, the previews I got were simple command line tables that were gone and forced me to open pgAdmin to further inspect the schema.&lt;/p&gt;

&lt;p&gt;I realized this was not sustainable in the long term, and I started building Envoy to manage my clients' database schema migrations seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Environment Management: Add/switch between dev, staging, prod databases&lt;/li&gt;
&lt;li&gt;Migration Editor: Write SQL with syntax highlighting&lt;/li&gt;
&lt;li&gt;Schema Preview: See changes before applying&lt;/li&gt;
&lt;li&gt;Permission Auditing: GRANT verification per user and environment&lt;/li&gt;
&lt;li&gt;Audit Trail: Complete history of all schema migration operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;All feedback is welcome! As I'm in the MVP stage, I'll be working on UI enhancements, finishing the environments CRUD, integrating both unit and integration tests in the codebase and simplifying the onboarding process.  &lt;/p&gt;

&lt;p&gt;You can support Envoy by giving it a star in the following repo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/franciscoluna-28/envoy" rel="noopener noreferrer"&gt;https://github.com/franciscoluna-28/envoy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leave a comment and share if you've found this tool useful :)&lt;/p&gt;

</description>
      <category>devops</category>
      <category>database</category>
      <category>postgres</category>
      <category>go</category>
    </item>
    <item>
      <title>The Founding Engineer’s Checklist: Every Essential System for a Battle-Tested Launch</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Wed, 18 Mar 2026 21:55:41 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/the-founding-engineers-checklist-every-essential-system-for-a-battle-tested-launch-1kae</link>
      <guid>https://dev.to/franciscolunadev82/the-founding-engineers-checklist-every-essential-system-for-a-battle-tested-launch-1kae</guid>
      <description>&lt;p&gt;I've developed products for startups. Most of them have failed and not because of the code. The backend was functional, queries were fast and the infrastructure could handle actual users, even if the traffic was small.&lt;/p&gt;

&lt;p&gt;Why did them fail? Lack of PMF, scope creep and tedious operation management that made the 24/7 IT support guy for months.  &lt;/p&gt;

&lt;p&gt;The real world of startup software development is not as easy as deploying your app and asking users to come; you also need to be able to add new features and see how users interact with your app. &lt;/p&gt;

&lt;p&gt;I had to learn these things with downtime and scope creep, but they will ensure you have a battle tested launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: This blog goes beyond rate limiting and using environment variables, often times, real world services are not mentioned in coding bootcamps or university.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔴 Critical - Infrastructure and Deployment
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Provisioning: Ensure you have your infrastructure documented, whether in &lt;a href="https://developer.hashicorp.com/terraform" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;, &lt;a href="https://www.pulumi.com/" rel="noopener noreferrer"&gt;Pulumi&lt;/a&gt; or simple documentation if your product doesn't have too many moving parts yet. Your infrastructure and setup needs to be replicable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CI/CD Pipelines: Make sure you have deployment scripts or actions to deploy your system and application. Always double check the secrets, permissions and environments available in your setup. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secrets Management: Annotate all your application secret templates in .env.example files, secure them in your cloud provider and make sure to rotate the credentials accordingly. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing: For God's sake, don't skip testing even for MVP. Prioritize testing inputs, happy paths and use cases. I recommend using Playwright to test the entire application workflow and integration tests to test your use cases.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🔴 Critical - Data Layer Reliability
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Database Migrations: My least favorite one. Make sure you have a clear way to do this. You can use your ORM built in migration tools, custom scripts, CI/CD pipelines or even manual deployments. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Backups &amp;amp; Recovery: Create backups of your database at least daily if you're using a managed database. Back up your server in case you're using a VPS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Caching Strategy: Make sure your Redis setup and materialized views work as expcected and have clear policies.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Remember this small checklist before running schema migrations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new field in a table should never be &lt;code&gt;NOT NULL&lt;/code&gt;. It should always have a default value or be nullable.&lt;/li&gt;
&lt;li&gt;Don't take normalization too seriously. Sometimes it's better to use JSONB over complicating a new product with new tables.&lt;/li&gt;
&lt;li&gt;Your new schema should be compatible with previous features. Whatever trade-offs you made, will have consequences later. &lt;/li&gt;
&lt;li&gt;Don't trust your ORM too much. Always double-check the SQL code yourself.&lt;/li&gt;
&lt;li&gt;Apply the principle of least privilege to everything. This means if your user needs to read the database, give them read-only access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;(I'm also building a tool to make this process less painful in PostgreSQL! - It'll be revealed soon.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔴 Critical - Identity and Security
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;JWT tokens: It's up to you if you use JWT with a long TTL or refresh tokens. What matters is that you save the JWT secret in an environment variable and you have a disaster recovery strategy if it gets exposed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auth: Don't reinvent the wheel. Keep it simple and don't implement more services than needed whether you use custom auth or a managed service. If you use a manage service, keep the data in your own database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RBAC &amp;amp; ABAC: Have your roles and policies documented. Create custom policies and checks to prevent repeating if statemements across your app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSL &amp;amp; Proper Configuration: Make sure your domains and infrastructure run over HTTPS, you have your robots.txt files, metadata, icons, SEO, app redirections and external services. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transactional Messaging: Set up a service such as &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt; or &lt;a href="https://aws.amazon.com/ses/" rel="noopener noreferrer"&gt;AWS SES&lt;/a&gt; and configure the SPF and DKIM records in your DNS. Use proper email domains and avoid spamming your users. To create templates you can use &lt;a href="https://mjml.io/" rel="noopener noreferrer"&gt;MJML&lt;/a&gt;, as it allows you to create professional and responsive emails.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're using images or fonts in your transactional emails, make sure they belong in your domain (e.g., images.mydomain.com/logo).&lt;/li&gt;
&lt;li&gt;Keep your emails as simple as possible and make sure your links work as expected. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🟡 Important - Observability
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Frontend Analytics: Don't let your marketing team go blind. You can use &lt;a href="https://umami.is/" rel="noopener noreferrer"&gt;Umami&lt;/a&gt; for this, but other tools may work as well. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Frontend error handling: You need to see if your application is not working well for other users. I recommend using &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt; to monitor your frontend errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logging: You need to use structured logging in services such as CloudWatch or BetterStack. Even a well structured Pino in a Docker container is valid if you're in the MVP stage. One important note, never display passwords or credentials in your loggers. Stick to non-sensitive fields like the user ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Health Checks and Alerts: Keep it simple for MVP. Use a service like &lt;a href="https://betterstack.com/" rel="noopener noreferrer"&gt;BetterStack&lt;/a&gt; or your cloud provider to ping your &lt;code&gt;/health&lt;/code&gt; endpoint every 2 minutes or thereabouts. This way you'll receive an email when one of your services is down. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server Monitoring and Observability: This step is not often needed if your system is small. You can implement &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; and &lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt; to monitor your infrastructure state (RAM usage, clusters metrics), although most cloud services have their built in monitors and metrics. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure to ping the database and other critical services such as Redis when calling your &lt;code&gt;/health&lt;/code&gt; endpoint. &lt;/li&gt;
&lt;li&gt;Prometheus and Grafana are powerful but high-maintenance. Stick to built-in cloud metrics for your MVP to keep your focus on the product, not the dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🟢 Good to Have - Operations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Internal modules: Make the admin modules and initial app configuration seamless to use. Remember marketing, product and your internal team also need to use the application. Give the other departments what they need so you're not the IT support guy all day :)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setup should be easy: You should not be using manual SQL and scripts every single time you deploy the system. Make the first app setup to initialize the platform intuitive, even if it's for yourself only. Don't develop hate towards your own system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Architecture and scope matters: I once had to build a product solo with no guidance. I built poor architecture and I took every single request from the client. And of course, it collapsed under its own weight... &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn software architecture (how to create code that can be tested and maintained in the future)&lt;/li&gt;
&lt;li&gt;Internal tooling is as important as customer facing features in most serious apps. Yes, your CEO and the marketing team are also your clients.&lt;/li&gt;
&lt;li&gt;Get the user count and basic metrics for the team please. This will save you hours of IT support.&lt;/li&gt;
&lt;li&gt;Learn about negotiation (how to set boundaries, build a MVP with proper scope and educate non-technical people about product development).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🟢 Good to have - Documentation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;OpenAPI documentation: &lt;a href="https://swagger.io/specification/" rel="noopener noreferrer"&gt;OpenAPI 3.0&lt;/a&gt; (OAS 3.0) is a specification you can use to document your API. You don't need it, but it makes collaboration and scalability easier. If you're building a serious product that will be maintained over time, consider adding it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Onboarding docs: Document the current state of the system and what needs to be done, feature flags, how to run things locally, important trade-offs made, the architecture and tech stack of the system and alongside the most critical technical debt to pay.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalability plans: They don't need to be AWS architecture diagrams yet; a Notion page is enough. But you need to identify the current bottlenecks in your system and have a plan to scale.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure your API has consistent response structures, examples and clear body data.&lt;/li&gt;
&lt;li&gt;Even if you have plans to work solo, the documentation is useful for your clients and your future self.&lt;/li&gt;
&lt;li&gt;Most bottlenecks to scale will show up in the server and application layer. For this, you'll need to use load balancers, consider serverless architecture or use scalability groups.
&lt;/li&gt;
&lt;li&gt;Node.js is good, but sometimes Golang can be better for specific services. Understand your technologies well.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;You can learn more about my work on my website, where I help companies build their MVPs and Backend Applications: &lt;a href="https://www.itsfranciscoluna.com/" rel="noopener noreferrer"&gt;https://www.itsfranciscoluna.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading! Leave a comment if you want to share other tips you know for a successful MVP launch :)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>discuss</category>
      <category>node</category>
    </item>
    <item>
      <title>The Trench Doctrine: A Third-World Founding Engineer's Post-Mortem</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Sun, 18 Jan 2026 16:33:16 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/the-trench-doctrine-a-third-world-founding-engineers-post-mortem-jk2</link>
      <guid>https://dev.to/franciscolunadev82/the-trench-doctrine-a-third-world-founding-engineers-post-mortem-jk2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Most people think I'm stubborn about startups, products and the standards I developed are insane.&lt;/p&gt;

&lt;p&gt;Maybe they are. But when you've faced user complaints directly, you have to manage a database at 12pm on a Saturday because of broken SQL scripts, phantom docker containers, extreme technical debt, talking with the CEO directly and falling for scope creep, you already know I had to learn how this game worked.&lt;/p&gt;

&lt;p&gt;Let's mention the 7 most important points and lessons I learned while being a founding engineer from a third world country, Venezuela.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Scope Creep
&lt;/h2&gt;

&lt;p&gt;I've seen this a lot. Adding features because they're cool or "just in case". When you're a founding engineer, there's a skill you must develop. Negotiating scope. Because the CEO and the non-technical part of your team want to add features all the time.&lt;/p&gt;

&lt;p&gt;I'm skeptical about scope nowadays. Because I was responsible of building products no one understood and I was saying yes to every single one of the CEO's ideas.&lt;/p&gt;

&lt;p&gt;My advice? Please, keep scope simple for MVP and introduce the core features. Iterate as you go and always validate your feedback with other users and the competition. &lt;/p&gt;

&lt;p&gt;Always prefer a tough discussion that'll save the project over sweet acceptance.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Poor Software Architecture
&lt;/h2&gt;

&lt;p&gt;Let's be honest. Proper software architecture is not overengineering. It's  about using the 20% of techniques and patterns that will give your codebase of results. This is called the Pareto principle.&lt;/p&gt;

&lt;p&gt;For a MVP you don't need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Utility classes&lt;/li&gt;
&lt;li&gt;Complex mappers&lt;/li&gt;
&lt;li&gt;Aggregators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DTOs&lt;/li&gt;
&lt;li&gt;Repositories&lt;/li&gt;
&lt;li&gt;Entities&lt;/li&gt;
&lt;li&gt;Handlers&lt;/li&gt;
&lt;li&gt;Use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core goal of software architecture is to make the software flexible and to iterate faster through decoupling. It's a simple concept: Your code should not be glued together like a wall, but rather it's a set of flexible Lego blocks you can work with. &lt;/p&gt;

&lt;p&gt;Because if your stakeholders ask you for a change of the requirements (spoiler: they will) or if you need to fix a bug you, you're not spending 5 hours going through spaghetti code but 30 minutes tweaking a use case, making things easier to test and deploy. &lt;/p&gt;




&lt;h2&gt;
  
  
  3. Complexity
&lt;/h2&gt;

&lt;p&gt;One day some guys on LinkedIn were talking to me about their architecture. They were telling me they were using microservices. I asked them about their DAU rate. It was less than 100. &lt;/p&gt;

&lt;p&gt;In that moment, I wanted to turn off the computer just to process this.&lt;/p&gt;

&lt;p&gt;The problem is not microservices themselves. They're a tool to be used once your company and products scale and you need to separate responsibilities. But are you aware of the maintenance overhead you're introducing with this choice?&lt;/p&gt;

&lt;p&gt;Choose a proper modular monolith when you're getting started, collect metrics, talk to users and iterate your business idea as you go.&lt;/p&gt;

&lt;p&gt;The simpler it's to get started and iterate, the more chances of winning you have. Because you have more room to test and experiment, and in the real world, users don't care about your API gateway, only if the feature works and if it's reliable enough. &lt;/p&gt;




&lt;h2&gt;
  
  
  4. Not everyone will get it
&lt;/h2&gt;

&lt;p&gt;Sometimes, it's better to be alone with senior engineers, CEOs, CTOs and those who are with you in the trenches.&lt;/p&gt;

&lt;p&gt;No. I'm not telling you to stop networking but to get your priorities right.&lt;/p&gt;

&lt;p&gt;When you're building a complex multi tenancy architecture, writing tests to prevent lawsuits and stress testing the system you don't need to be with people who are asking you how the Python syntax works or how they can  center a div.&lt;/p&gt;

&lt;p&gt;The best choice you can make is to bet on yourself, your future, skills and the people who are with you in the trenches.&lt;/p&gt;

&lt;p&gt;Mentor others and document, but value your time for deep work.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Testing DOES matter
&lt;/h2&gt;

&lt;p&gt;I still remember the tragic day of launch where a major stakeholder found they could not use specific characters when trying to login into the system. We had little to no tests and we were assuming our input DTOs were working fine.&lt;/p&gt;

&lt;p&gt;Spoiler, they were not. You don't need 100% test coverage, but always consider testing the following modules through both unit and integration tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Input DTOs&lt;/li&gt;
&lt;li&gt;Permissions&lt;/li&gt;
&lt;li&gt;Access control systems&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Academia might betray you
&lt;/h2&gt;

&lt;p&gt;I'm not telling you to stop studying or learning, but the opposite, knowledge is power. However, you need to develop pragmatism and avoid falling for dogmatism (dogma).&lt;/p&gt;

&lt;p&gt;Dogma is having the belief things should always be done in a specific way no matter the situation. Pragmatism, on the other hand, is knowing when to break a specific rule while considering the trade-offs.&lt;/p&gt;

&lt;p&gt;Look, once I designed 35 tables using the 5 possible NFs of Database Design for MVP.&lt;/p&gt;

&lt;p&gt;For what? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;87 users in 2 months&lt;/li&gt;
&lt;li&gt;Complex queries with 3 joins &lt;/li&gt;
&lt;li&gt;Servers down&lt;/li&gt;
&lt;li&gt;User complaints &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I read Designing Data Intensive Applications and I learned it was fine to just have a JSONB row in my tables, it changed my life.&lt;/p&gt;

&lt;p&gt;Because sometimes the only way to win and keep your sanity is to break the rules. Sadly, this is something you learn only through experience.&lt;/p&gt;

&lt;p&gt;Which is why I'll still insist about avoiding dogma.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Life outside startups
&lt;/h2&gt;

&lt;p&gt;I wish someone would've told me these things before. I learned these the hard way and I hope you can learn from them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protect your relationships at all cost&lt;/li&gt;
&lt;li&gt;Learn how to read and understand legal contracts&lt;/li&gt;
&lt;li&gt;Learn about human psychology and power dynamics&lt;/li&gt;
&lt;li&gt;Learn how to say no&lt;/li&gt;
&lt;li&gt;Learn how to negotiate&lt;/li&gt;
&lt;li&gt;The startup is a medium, not the end&lt;/li&gt;
&lt;li&gt;Equity is NOT free. Make sure it aligns with your future plans&lt;/li&gt;
&lt;li&gt;Save yourself. Not a company. Not a startup. Your own self&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As Ging Freecs says:&lt;/p&gt;

&lt;p&gt;"You should enjoy the little detours to the fullest. Because that's where you'll find things more important than what you want."&lt;/p&gt;

&lt;p&gt;Thank you for reading. Don't take this advice literally. This is only my philosophy about startups at 21.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>learning</category>
      <category>startup</category>
      <category>networking</category>
    </item>
    <item>
      <title>An Introduction to Docker: Stop asking your stakeholders to install Postgres! 🚀</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Sun, 11 Jan 2026 20:32:58 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/an-introduction-to-docker-stop-asking-your-stakeholders-to-install-postgres-2a57</link>
      <guid>https://dev.to/franciscolunadev82/an-introduction-to-docker-stop-asking-your-stakeholders-to-install-postgres-2a57</guid>
      <description>&lt;p&gt;I once asked a major stakeholder to install Postgres on his laptop just to see my progress.&lt;/p&gt;

&lt;p&gt;It was September 2024. I was earlier in my career, and the shame of that moment still fuels my obsession with Docker and DX today.&lt;/p&gt;

&lt;p&gt;The project was in its early stages. When the stakeholder asked to try it himself, I gave him a to-do list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Install Node.js and PNPM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up a local Postgres instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switch to a Linux environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manually configure credentials and run migrations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't share a project, I shared a liability. Today, I don't ship code; I ship environments.&lt;/p&gt;

&lt;p&gt;That experience was my wake-up call. I realized that as a developer, my job isn't just to write code, but delivering value. If a stakeholder or a new teammate can't run the project in minutes, I've failed.&lt;/p&gt;

&lt;p&gt;I'll help you save time and be more effective with one of my favorite development tools ever created: &lt;strong&gt;Docker&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why using Docker? Is not that a DevOps thing?
&lt;/h2&gt;

&lt;p&gt;Yes. Docker is used for DevOps. But almost no one tells you it's used for professional development environments. Would you prefer to install Docker Desktop and run a few commands to start building or:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Manually set up Postgres in your machine&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up WSL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure each service is running&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up each service individually&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure the versions match&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Etc&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This process took my company's onboarding time from 5 days to a couple of hours. &lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started with Docker
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install Docker Desktop
&lt;/h3&gt;

&lt;p&gt;The industry standard for Mac and Windows. If you’re on Linux, you already know the drill. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop for Windows&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Verify Installation
&lt;/h3&gt;

&lt;p&gt;Open your terminal and check the installed version&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
docker &lt;span class="nt"&gt;--version&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Your First Container
&lt;/h3&gt;

&lt;p&gt;Run this to see Docker pull an image and spin it up in seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
docker run hello-world

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  About Docker
&lt;/h2&gt;

&lt;p&gt;Docker lets you package your app with everything it needs to run; from code to runtime, and libraries into a single unit called Container; which can run in any OS and machine.&lt;/p&gt;

&lt;p&gt;It's like a bassline. You don't notice it's important until it's gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Docker vs Virtual Machines (VMs)
&lt;/h2&gt;

&lt;p&gt;VMs are heavy, they require a full copy of the operating system and consume significant RAM and CPU. &lt;/p&gt;

&lt;p&gt;On the other hand, Containers share your machine's OS kernel and they only install the libraries and dependencies they need. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Pieces of Docker
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Dockerfile: This is your blueprint. It allows you to specify Docker how you want to build the environment (OS, the Node.js version, and your dependencies)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Image: The snapshot. It's what you get when you build the blueprint given by the dockerfile. It's a read-only template that contains your application and its environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Container: This is the image in action. You can start, stop and delete environments without affecting your actual computer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Solving the "Postgres Headache" with Docker Compose
&lt;/h2&gt;

&lt;p&gt;Remember the list I gave that stakeholder? "Install Postgres, run migrations, set credentials..."&lt;/p&gt;

&lt;p&gt;With Docker Compose, we turn that entire manual process into a single command: &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Think of each service (application, database, message queue) as an instrument. You're combining them to create a band, but in YAML.&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%2Fk40gxd5caqz90071sbwn.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%2Fk40gxd5caqz90071sbwn.png" alt="Docker Compose Diagram" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's important to mention this tool is used for development environments and small MVPs in production. You'll need load balancers and eventually K8s for scaling a complex system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time to Build
&lt;/h3&gt;

&lt;p&gt;Suppose we want to dockerize a Next.js app and run a database with it, we're going to create 3 files in the root folder of the project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;docker-compose.yaml&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;.dockerignore&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;dockerfile&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;

&lt;p&gt;This is how Docker will build the image to run our Next.js application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pnpm

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json pnpm-lock.yaml* ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["pnpm", "dev"]&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Dockerignore
&lt;/h2&gt;

&lt;p&gt;Just as we have our &lt;code&gt;.gitignore&lt;/code&gt; we also have &lt;code&gt;.dockerignore&lt;/code&gt;. This file is responsible for preventing packaging sensitive or unnecessary content from the Next.js app into the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.pnpm-debug.log

.next
out
build

.env
.env.local
.env*.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

.git
.gitignore

.DS_Store
Thumbs.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Docker Compose File
&lt;/h2&gt;

&lt;p&gt;This file is responsible for orchestrating the containers. You declare each component of your application as a service. Remember to create a Dockerfile for the core Next.js app. The data of postgres will persist on the volume we have created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextjs-db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;supersecretpassword&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextjs-app&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://admin:supersecretpassword@db:5432/db&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pnpm dev&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

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

&lt;/div&gt;






&lt;p&gt;Notice I use &lt;code&gt;node:20-alpine&lt;/code&gt;. It uses Alpine Linux under the hood; you can choose other Node.js images depending on your needs. Remember that in production environments, we optimize for image size and security surface. We also use &lt;code&gt;depends_on&lt;/code&gt; because your app shouldn't start if the database isn't alive.&lt;/p&gt;

&lt;p&gt;We're saving the database data in a named volume called &lt;code&gt;postgres_data&lt;/code&gt;. This way, when we turn off the container, the data will still persist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For resilience, combine depends_on with a healthcheck to ensure the DB is actually ready to receive traffic, not just "running".&lt;/p&gt;




&lt;h2&gt;
  
  
  One Command to Rule them All
&lt;/h2&gt;

&lt;p&gt;With these three files in your root directory, you’ve turned a 1-hour headache setup into a 10-second command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By running this, Docker will build your Next.js app, pull the database image, set up the network between them, and start your dev server with hot-reload enabled.&lt;/p&gt;

&lt;p&gt;You can even add startup and seeding scripts if needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stop Selling Hours, Start Delivering Systems
&lt;/h2&gt;

&lt;p&gt;I used to think Docker was only a complex DevOps tool until that afternoon in 2024. Where I learned that seniority isn't just about writing fancy code, but about using code as a tool for the business.&lt;/p&gt;

&lt;p&gt;I stopped being just a programmer and became a Software Engineer. I reclaimed my time, improved my team's DX, and made sure that no stakeholder ever has to install a database on their laptop again.&lt;/p&gt;

&lt;p&gt;If you want to scale your project or your career, start containerizing today. Your future self and your team will thank you.&lt;/p&gt;

&lt;p&gt;What was your worst setup story? Share below in the comments!&lt;/p&gt;




&lt;h3&gt;
  
  
  Building a complex application or a high-performance system?
&lt;/h3&gt;

&lt;p&gt;I help startups engineer scalable applications and internal tools that stay fast as they grow. See how I build scalable systems here: 👉 &lt;a href="//www.itsfranciscoluna.com"&gt;www.itsfranciscoluna.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>productivity</category>
      <category>learning</category>
    </item>
    <item>
      <title>Learn Docker: Stop asking your stakeholders to install Node.js</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Sat, 10 Jan 2026 13:36:13 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/stop-asking-your-stakeholders-to-install-nodejs-p3d</link>
      <guid>https://dev.to/franciscolunadev82/stop-asking-your-stakeholders-to-install-nodejs-p3d</guid>
      <description>&lt;p&gt;I once asked a major stakeholder to install Postgres on his laptop just to see my progress.&lt;/p&gt;

&lt;p&gt;It was September 2024. I was earlier in my career, and the shame of that moment still fuels my obsession with Docker and DX today.&lt;/p&gt;

&lt;p&gt;The project was in its early stages. When the stakeholder asked to try it himself, I gave him a to-do list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Install Node.js and PNPM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up a local Postgres instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switch to a Linux environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manually configure credentials and run migrations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't share a project, I shared a liability. Today, I don't ship code; I ship environments.&lt;/p&gt;

&lt;p&gt;That experience was my wake-up call. I realized that as a developer, my job isn't just to write code, but delivering value. If a stakeholder or a new teammate can't run the project in minutes, I've failed.&lt;/p&gt;

&lt;p&gt;I'll help you save time and be more effective with one of my favorite development tools ever created: &lt;strong&gt;Docker&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why using Docker? Is not that a DevOps thing?
&lt;/h2&gt;

&lt;p&gt;Yes. Docker is used for DevOps. But almost no one tells you it's used for professional development environments. Would you prefer to install Docker Desktop and run a few commands to start building or:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually set up Postgres in your machine&lt;/li&gt;
&lt;li&gt;Set up WSL&lt;/li&gt;
&lt;li&gt;Ensure each service is running&lt;/li&gt;
&lt;li&gt;Set up each service individually&lt;/li&gt;
&lt;li&gt;Ensure the versions match&lt;/li&gt;
&lt;li&gt;Etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This process took my company's onboarding time from 5 days to a couple of hours. &lt;/p&gt;




&lt;h2&gt;
  
  
  About Docker
&lt;/h2&gt;

&lt;p&gt;Docker lets you package your app with everything it needs to run; from code to runtime, and libraries into a single unit called Container; which can run in any OS and machine.&lt;/p&gt;

&lt;p&gt;It's like a bassline. You don't notice it's important until it's gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Docker vs Virtual Machines (VMs)
&lt;/h2&gt;

&lt;p&gt;VMs are heavy, they require a full copy of the operating system and consume significant RAM and CPU. &lt;/p&gt;

&lt;p&gt;On the other hand, Containers share your machine's OS kernel and they only install the libraries and dependencies they need. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Pieces of Docker
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Dockerfile: This is your blueprint. It allows you to specify Docker how you want to build the environment (OS, the Node.js version, and your dependencies)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Image: The snapshot. It's what you get when you build the blueprint given by the dockerfile. It's a read-only template that contains your application and its environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Container: This is the image in action. You can start, stop and delete environments without affecting your actual computer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Solving the "Postgres Headache" with Docker Compose
&lt;/h2&gt;

&lt;p&gt;Remember the list I gave that stakeholder? "Install Postgres, run migrations, set credentials..."&lt;/p&gt;

&lt;p&gt;With Docker Compose, we turn that entire manual process into a single command: &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Think of each service (application, database, message queue) as an instrument. You're combining them to create a band, but in YAML.&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%2Fk40gxd5caqz90071sbwn.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%2Fk40gxd5caqz90071sbwn.png" alt="Docker Compose Diagram" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's important to mention this tool is used for development environments and small MVPs in production. You'll need load balancers and eventually K8s for scaling a complex system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time to Build
&lt;/h3&gt;

&lt;p&gt;Suppose we want to dockerize a Next.js app and run a database with it, we're going to create 3 files in the root folder of the project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docker-compose.yaml&lt;/li&gt;
&lt;li&gt;.dockerignore&lt;/li&gt;
&lt;li&gt;dockerfile&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;

&lt;p&gt;This is how Docker will build the image to run our Next.js application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pnpm

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json pnpm-lock.yaml* ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["pnpm", "dev"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Dockerignore
&lt;/h2&gt;

&lt;p&gt;Just as we have our &lt;code&gt;.gitignore&lt;/code&gt; we also have &lt;code&gt;.dockerignore&lt;/code&gt;. This file is responsible for preventing packaging sensitive or unnecessary content from the Next.js app into the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.pnpm-debug.log

.next
out
build

.env
.env.local
.env*.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

.git
.gitignore

.DS_Store
Thumbs.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Docker Compose File
&lt;/h2&gt;

&lt;p&gt;This file is responsible for orchestrating the containers. You declare each component of your application as a service. Remember to create a Dockerfile for the core Next.js app. The data of postgres will persist on the volume we have created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextjs-db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;supersecretpassword&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextjs-app&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://admin:supersecretpassword@db:5432/db&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pnpm dev&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Notice I use &lt;code&gt;node:20-alpine&lt;/code&gt;. It uses Alpine Linux under the hood; you can choose other Node.js images depending on your needs. Remember that in production environments, we optimize for image size and security surface. We also use &lt;code&gt;depends_on&lt;/code&gt; because your app shouldn't start if the database isn't alive.&lt;/p&gt;

&lt;p&gt;We're saving the database data in a named volume called &lt;code&gt;postgres_data&lt;/code&gt;. This way, when we turn off the container, the data will still persist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For resilience, combine depends_on with a healthcheck to ensure the DB is actually ready to receive traffic, not just "running".&lt;/p&gt;




&lt;h2&gt;
  
  
  One Command to Rule them All
&lt;/h2&gt;

&lt;p&gt;With these three files in your root directory, you’ve turned a 1-hour headache setup into a 10-second command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By running this, Docker will build your Next.js app, pull the database image, set up the network between them, and start your dev server with hot-reload enabled.&lt;/p&gt;

&lt;p&gt;You can even add startup and seeding scripts if needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stop Selling Hours, Start Delivering Systems
&lt;/h2&gt;

&lt;p&gt;I used to think Docker was only a complex DevOps tool until that afternoon in 2024. Where I learned that seniority isn't just about writing fancy code, but about using code as a tool for the business.&lt;/p&gt;

&lt;p&gt;I stopped being just a programmer and became a Software Engineer. I reclaimed my time, improved my team's DX, and made sure that no stakeholder ever has to install a database on their laptop again.&lt;/p&gt;

&lt;p&gt;If you want to scale your project or your career, start containerizing today. Your future self and your team will thank you.&lt;/p&gt;

&lt;p&gt;What was your worst setup story? Share below in the comments!&lt;/p&gt;




&lt;h3&gt;
  
  
  Building a complex application or a high-performance system?
&lt;/h3&gt;

&lt;p&gt;I help startups engineer scalable applications and internal tools that stay fast as they grow. See how I build scalable systems here: 👉 &lt;a href="//www.itsfranciscoluna.com"&gt;www.itsfranciscoluna.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Junior vs. Lead: The 8 High-Stakes Mistakes I’m Leaving in 2025</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Tue, 23 Dec 2025 20:38:32 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/junior-vs-lead-the-8-high-stakes-mistakes-im-leaving-in-2025-2klf</link>
      <guid>https://dev.to/franciscolunadev82/junior-vs-lead-the-8-high-stakes-mistakes-im-leaving-in-2025-2klf</guid>
      <description>&lt;p&gt;In 2025, I learned that the distance between a junior Engineer and a lead is measured in the mistakes you stop making. It was a year of challenging product launches and pivots that changed my technical philosophy. I didn't just ship code; I paid the cost of admission in the form of technical debt and manual labor. &lt;/p&gt;

&lt;p&gt;That's when I learned that efficiency is a business strategy, not just a technical preference. Here are the major mistakes I made during my professional career in 2025.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Poor Communication with Stakeholders
&lt;/h2&gt;

&lt;p&gt;I once told a Product Manager:&lt;/p&gt;

&lt;p&gt;"I’ve configured the NGINX reverse proxy to support 5MB image uploads."&lt;/p&gt;

&lt;p&gt;Technically true, but useless to them.&lt;/p&gt;

&lt;p&gt;As you grow, your job shifts from writing code to solving business problems. Stakeholders don't care about the how; they care about the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Shift:
&lt;/h3&gt;

&lt;p&gt;Junior: "I updated the NGINX config."&lt;/p&gt;

&lt;p&gt;Lead: "Users can now upload high-res images. The marketing bottleneck is gone."&lt;/p&gt;

&lt;p&gt;It's the same as when you play a guitar solo. Of course you're not going to tell people the fancy scale you're using. They only care about the feeling.&lt;/p&gt;

&lt;p&gt;Stakeholders care about results and making the product better, not much about the fancy tech you use.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Infrastructure as Manual Labor
&lt;/h2&gt;

&lt;p&gt;If your "deployment process" involves opening pgAdmin to manually add users, or if your documentation is a list of 20 manual steps to configure a server, you’ve fallen for the Sysadmin Trap.&lt;/p&gt;

&lt;p&gt;It's the same when you have to set up the entire AWS architecture using raw dashboards.&lt;/p&gt;

&lt;p&gt;You are acting as a manual gatekeeper instead of an architect.&lt;/p&gt;

&lt;p&gt;Manual steps are invitations for human error. If you have to "remember" to set a config variable or manually assign a role to a new team member, you haven't built a system but a bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Shift:
&lt;/h3&gt;

&lt;p&gt;Junior: Manages the database and servers through a GUI (pgAdmin/AWS Console) and manual docs.&lt;/p&gt;

&lt;p&gt;Lead: Automates the environment. Use Migration Scripts to manage roles and Ansible/Terraform to define infrastructure.&lt;/p&gt;

&lt;p&gt;The business shouldn't depend on your memory to set things up. It should depend on your code.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Configuration Overhead
&lt;/h2&gt;

&lt;p&gt;Rebuilding a Docker image just to change a URL is a failure of architecture.&lt;/p&gt;

&lt;p&gt;I’ve seen it happen: a stakeholder decides to change a domain or an API endpoint, and the engineering team has to trigger a full CI/CD pipeline, wait for the build, and redeploy everything for a single string change...&lt;/p&gt;

&lt;p&gt;You should never have to rebuild your code to change how it behaves in its environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Hardcoding Behavior
&lt;/h3&gt;

&lt;p&gt;Whether it's a "feature flag," a timeout limit, or a third-party URL, if it's baked into your source code or your Dockerfile, your system is rigid. Rigid systems break under the pressure of fast-moving startups.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Shift:
&lt;/h3&gt;

&lt;p&gt;Junior: Hardcodes "minor" settings or bakes them into the Docker image during build time.&lt;/p&gt;

&lt;p&gt;Lead: Uses Environment Variables and Secrets Managers (like AWS Secrets Manager or Parameter Store).&lt;/p&gt;

&lt;p&gt;Ensure that the Docker image is immutable. It should be the exact same image in Staging as it is in Production. The only thing that changes is the environment it plugs into.&lt;/p&gt;

&lt;p&gt;This doesn't just save time, it saves the business from deployment fatigue and unnecessary downtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Poor Testing Strategies
&lt;/h2&gt;

&lt;p&gt;If your "Testing Strategy" is just you clicking around the UI before a deploy, you don't have a strategy, you have a prayer.&lt;/p&gt;

&lt;p&gt;I once saw a production launch stumble because a user couldn't use a "dot" in their username. It sounds small until it’s the reason your conversion rate drops by 15% on launch day.&lt;/p&gt;

&lt;p&gt;You need to set up testing from day one and let your CI/CD pipeline be the "bouncer" that keeps bad code out of production.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Actually Matters (The 80/20 Rule):
&lt;/h3&gt;

&lt;p&gt;You don’t need 100% code coverage. That’s a vanity metric. You need to protect the Critical Path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Permissions &amp;amp; Validations: Can a user see data they shouldn't? Can they sign up with valid characters?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Critical Business Logic: Does the checkout work? Does the subscription upgrade trigger correctly?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edge Cases &amp;amp; Race Conditions: What happens if two users click "buy" on the last item at the exact same millisecond?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Shift:
&lt;/h3&gt;

&lt;p&gt;Junior: Tests manually and hopes for the best. Thinks tests "slow them down."&lt;/p&gt;

&lt;p&gt;Lead: Uses Unit Tests for logic, Integration Tests for the database/API flow, and E2E for the "happy path."&lt;/p&gt;

&lt;p&gt;I know that tests aren't about finding bugs; they are about confidence. They allow the team to move fast without the fear of breaking the product every time they push code.&lt;/p&gt;

&lt;p&gt;Set standards when writing tests and ensure your team is proactively working on them as well.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Poor Permission Management
&lt;/h2&gt;

&lt;p&gt;I still remember these checks as a form of technical debt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Execute logic to ban user&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fine for an MVP. But as the system grows, combining business logic with hardcoded roles becomes a nightmare.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Role Inflation
&lt;/h3&gt;

&lt;p&gt;Imagine we're building a social media platform. Initially, Admins ban users and regular Users view posts. Simple.&lt;/p&gt;

&lt;p&gt;Then the business scales. You introduce a Moderator role. They need to manage reports and suspend accounts, but they shouldn't have full Admin access to the billing system. Suddenly, your code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;moderator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Execute logic to suspend account&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine this check is scattered across 50 different files. If the CEO decides to add a "Junior Moderator" with limited powers, you have to tweak every single if statement. Congrats, now you're fighting with your own system.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Shift: Ability-Based Access
&lt;/h3&gt;

&lt;p&gt;A Lead Engineer stops thinking about who the user is and starts thinking about what they are allowed to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ability&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;suspend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Execute logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Transformation:
&lt;/h3&gt;

&lt;p&gt;Junior: Checks the "ID card" (Role) at every door.&lt;/p&gt;

&lt;p&gt;Lead: Implements a "Keycard System" (Abilities/Permissions).&lt;/p&gt;

&lt;p&gt;By using a library like CASL or a specialized Policy class, you decouple the business logic from the organizational hierarchy. When the business pivots or adds new roles, you change one configuration file, not the entire codebase.&lt;/p&gt;

&lt;p&gt;And by the way, your tests will be simpler and auditing your permissions is a walk in the park now!&lt;/p&gt;




&lt;h2&gt;
  
  
  6. No Seeding Strategies
&lt;/h2&gt;

&lt;p&gt;I used to spend 20–30 minutes manually clicking through the UI, creating fake accounts, and spamming forms just to get "real-world" data to work with.&lt;/p&gt;

&lt;p&gt;I was doing the work of a script, but manually.&lt;/p&gt;

&lt;p&gt;Seeding is the process of populating your database with a predictable set of data for development and testing. Without it, you aren't engineering; you're just messing around.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Seeding is a Business Strategy:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Handling Edge Cases: You can instantly recreate that one specific, weird bug that only happens to users with 50+ transactions and an expired subscription.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stress Testing: You don't need to "spam" the system. One command generates 100,000 records to see if your PostgreSQL queries or Redis cache actually hold up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instant Onboarding: A new engineer (or a collaborator) should be able to clone the repo, run one command, and have a fully functional environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing the "Golden Path": Imagine you are debugging business logic for premium subscribers. Every time you want to test a change, you have to manually trigger a Stripe checkout, click through 5 different screens, and wait for webhooks. That's 10 minutes of manual labor per test. A seeding script handles this in 2 seconds. If you don't automate your state, you aren't testing; you're just doing expensive data entry.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Shift:
&lt;/h3&gt;

&lt;p&gt;Junior: Manually creates "test_user_123" every morning.&lt;/p&gt;

&lt;p&gt;Lead: Automates the state of the world.&lt;/p&gt;

&lt;p&gt;If you don't have a seeding strategy, you’re wasting the company’s most expensive resource: &lt;strong&gt;your time.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The "Hard Delete" Sin
&lt;/h2&gt;

&lt;p&gt;In a startup, data is the only asset you can't recreate. I used to think that DELETE FROM was just part of the CRUD flow. I was wrong.&lt;/p&gt;

&lt;p&gt;A hard delete is an irreversible loss of business knowledge. If a user deletes their account today, but the CEO wants to know why churn increased six months from now, that data is gone. The business no longer has access to the history.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Shift:
&lt;/h3&gt;

&lt;p&gt;Junior: Deletes records to "keep the database clean."&lt;/p&gt;

&lt;p&gt;Lead: Implements Soft Deletes (deleted_at timestamps) and Audit Logs.&lt;/p&gt;

&lt;p&gt;Yes, it's kind of tedious to only update a flag. However, it's critical for audit purposes and compliance. &lt;/p&gt;




&lt;h2&gt;
  
  
  8. Thinking a Company Was Going to Save Me
&lt;/h2&gt;

&lt;p&gt;This was the hardest lesson of 2025. I spent too much time being reactive, waiting for a "safe" job or a promotion to change my life.&lt;/p&gt;

&lt;p&gt;I realized that my security doesn't come from a contract; it comes from my reputation, my Network, and my skills. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Shift:
&lt;/h3&gt;

&lt;p&gt;Junior: Waits for instructions and a paycheck.&lt;/p&gt;

&lt;p&gt;Lead: Acts as a business ally. I treat every project like I’m a partner, and I focus the most on my own knowledge and skills.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR — Engineering Maturity Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Communicate outcomes, not configs&lt;/strong&gt;: Stakeholders care about results, not technical steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate infrastructure (IaC)&lt;/strong&gt;: Use Terraform/Ansible; don’t be a manual gatekeeper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep configs flexible&lt;/strong&gt;: No hardcoding; rely on environment variables and secrets managers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test for confidence, not coverage&lt;/strong&gt;: Protect the critical path with unit, integration, and E2E tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design permissions with abilities&lt;/strong&gt;: Move beyond role checks; implement ability‑based access control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seed your environments&lt;/strong&gt;: Automate realistic data for onboarding, edge cases, and stress testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserve data with soft deletes&lt;/strong&gt;: Use &lt;code&gt;deleted_at&lt;/code&gt; flags and audit logs; never throw away business knowledge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Own your growth&lt;/strong&gt;: Security comes from skills, reputation, and network — not contracts.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About 2026 and the Future
&lt;/h2&gt;

&lt;p&gt;If you’ve made it this far, thank you for reading! :)&lt;/p&gt;

&lt;p&gt;I wish you a Merry Christmas and a productive New Year with your family; of course, if you celebrate it.&lt;/p&gt;

&lt;p&gt;To start 2026 right, I’m currently diving into The Pragmatic Programmer. My goal is to become a more effective leader for my team and to ship features that are not just fast, but reliable.&lt;/p&gt;

&lt;p&gt;I’m also returning to pen, paper, and whiteboards. I’ve realized that relying too much on AI and digital tools made me drift. There is no substitute for the clarity of drawing a system by hand before writing a single line of code.&lt;/p&gt;

&lt;p&gt;Talk to other engineers outside your startup, rest, and plan your days. This is a marathon, not a race.&lt;/p&gt;

&lt;p&gt;Here’s to a year of less entropy and more architecture.&lt;/p&gt;

&lt;p&gt;What was your biggest lesson in moving from Junior to Lead? I'd love to hear from you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a complex application or a high-performance system?
&lt;/h3&gt;

&lt;p&gt;I help startups engineer scalable applications and internal tools that stay fast as they grow. See how I build scalable systems here: 👉 &lt;a href="//www.itsfranciscoluna.com"&gt;www.itsfranciscoluna.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>🚀 The Black Box Principle: Decoupling API Clients with OpenAPI and TypeScript</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Sat, 01 Nov 2025 18:54:05 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/the-black-box-principle-decoupling-api-clients-with-openapi-and-typescript-m91</link>
      <guid>https://dev.to/franciscolunadev82/the-black-box-principle-decoupling-api-clients-with-openapi-and-typescript-m91</guid>
      <description>&lt;p&gt;When I was primarily a frontend engineer, I had a terrible relationship with APIs. If I wasn't manually rewriting types and interfaces from the backend, I was debugging runtime errors because someone changed an endpoint without telling me. &lt;/p&gt;

&lt;p&gt;Back then I read the following advice: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;'The best way to get types from the server was to get the types from it.'&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;What I didn't realize was this was some of the worst advice I've ever received; until I became the backend engineer responsible for maintaining both sides.&lt;/p&gt;

&lt;p&gt;The code you're about to see isn't a hypothetical example. It's the actual technical debt that almost burned me out as a solo engineer building a startup. &lt;/p&gt;

&lt;p&gt;I didn't understand how the backend was supposed to work in a real world environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So this is what I came up with:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Are you telling me I also need the backend to JUST get some schemas? &lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;SignInWithEmailOrUsernameResponseSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SignInWithEmailOrUsernameSchema&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/backend/validations/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// What is this? Your own flaky OpenAPI schema?&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AuthAPI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useSignIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;useMutation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;SignInWithEmailOrUsernameResponseSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;AxiosError&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ErrorResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;SignInWithEmailOrUsernameSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;AuthAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// Only God knows how this was processed...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Tragedy
&lt;/h2&gt;

&lt;p&gt;You might be wondering. Francisco, what is wrong with this, really? Everything, at least when you're working with both a separate backend and frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ 1. Coupling
&lt;/h3&gt;

&lt;p&gt;You're assuming your monolith repository will exist forever. But what happens if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The backend moves to another repository?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You transition to microservices?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need to support multiple client applications?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The backend team starts using languages other than TypeScript?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💀 2. Compilation and build nightmares
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wait, why is my frontend bundle including this?&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserEntity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/backend/entities/User&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PasswordHelper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/backend/utils/crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DatabaseConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/backend/config/database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bundle Bloat: Your frontend is now shipping backend entities, utilities, and configuration files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build Complexity: Your CI/CD now needs the entire backend codebase just to build the frontend&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dependency Hell: Backend-specific packages leaking into frontend builds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker Disasters: Trying to untangle this mess in containerization&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ⚠ 3. Hardcoding and context switching
&lt;/h3&gt;

&lt;p&gt;This was the workflow with the former setup:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the new API endpoint&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This needs to be extended or changed manually 😭&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AUTH_API_ENDPOINTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;LOGIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_ENDPOINT_NAMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/login`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;REGISTER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_ENDPOINT_NAMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/register`&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create the new API service&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Yes, we also need backend dependencies and duplicate logic! &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;SignInRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// ← Manual type inference&lt;/span&gt;
  &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;SignInResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;           &lt;span class="c1"&gt;// ← More manual work&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AUTH_API_ENDPOINTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOGIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// ← Hardcoded string&lt;/span&gt;
  &lt;span class="na"&gt;requestSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SignInRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// ← Duplicated validation&lt;/span&gt;
  &lt;span class="na"&gt;responseSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SignInResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// ← More duplication&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;withCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create the queries and mutations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You already saw the code in the introduction, but see how duplicate things are and how prone this is to break, again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useSignIn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;useMutation&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;SignInWithEmailOrUsernameResponseSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;AxiosError&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ErrorResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;SignInWithEmailOrUsernameSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;AuthAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// Why are we guessing types? &lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Now pray that something does not break and repeat the same exact process for 60+ API endpoints... right?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  There's a BETTER way
&lt;/h2&gt;

&lt;p&gt;I performed a refactor at work where we migrated all this Zod chaos, DTOs, schema validations, and backend spaghetti into a proper API contract.&lt;/p&gt;

&lt;p&gt;Before I walk you through the recovery, let me introduce the tool that changed everything: OpenAPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAPI
&lt;/h2&gt;

&lt;p&gt;OpenAPI is a specification format used to describe and document REST APIs in a way that can be understood by human and used by machine. It’s not just documentation, but also a contract.&lt;/p&gt;

&lt;p&gt;Here’s what it gives you out of the box:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🗺 1. Available endpoints:&lt;/strong&gt;&lt;br&gt;
Every route, method, and path is clearly defined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🛠 2. Query and path params for each API endpoint:&lt;/strong&gt;&lt;br&gt;
You know exactly what each endpoint expects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ 3. Response format for each status code:&lt;/strong&gt;&lt;br&gt;
You can validate what success, failure, and edge cases look like.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We'll see now how we can create a professional API specification on the server side.&lt;/em&gt;*&lt;/p&gt;
&lt;h2&gt;
  
  
  Generating OpenAPI Schemas
&lt;/h2&gt;

&lt;p&gt;This process involves generating a .json or .yaml file containing your API contract. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning: This step varies dramatically based on:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The scale of your project (monolith vs microservices)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your framework (Nest.js vs Spring Boot vs Go)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your team's technical maturity (senior engineers vs junior team)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your deployment complexity (do you need API versioning?)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  The Nest.js Approach: Decorator-Driven
&lt;/h2&gt;

&lt;p&gt;In Nest.js you use decorators to automatically generate your OpenAPI spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// task.controller.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TasksController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiOkResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TaskEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Returns all tasks for the authenticated user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiUnauthorizedResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid or missing token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tasksService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// task.entity.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiProperty&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unique task identifier&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;550e8400-e29b-41d4-a716-446655440000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiProperty&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Task title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finish API documentation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiProperty&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;When the task was created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024-01-15T10:30:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Reality Check
&lt;/h2&gt;

&lt;p&gt;If look at Reddit or technical blogs/forums, some developers prefer writing OpenAPI specs manually. The code-first approach has trade-offs:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Pros:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Single source of truth (your code generates docs)&lt;/li&gt;
&lt;li&gt;Harder for docs to drift from implementation&lt;/li&gt;
&lt;li&gt;Faster iteration in early-stage startups&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Cons:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Can get messy with complex APIs&lt;/li&gt;
&lt;li&gt;Limited control over exact OpenAPI features&lt;/li&gt;
&lt;li&gt;Decorator pollution in your business logic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Framework for Success
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Mirror your domain entities and find a DRY way to manage them&lt;/li&gt;
&lt;li&gt;Document controllers methodically. Descriptions and examples are important too&lt;/li&gt;
&lt;li&gt;Display with SwaggerUI or Scalar for team collaboration&lt;/li&gt;
&lt;li&gt;Generate client SDKs automatically as part of your CI/CD&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Choosing Your API Visualizer
&lt;/h2&gt;

&lt;p&gt;Honestly? Whatever. The important thing is that you have a visual interface for your API. Choose the one that's the most suitable for your team needs. Let's explore the common options with more detail:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swagger UI - The battle-tested veteran&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Universally recognized, extensive customization&lt;/li&gt;
&lt;li&gt;❌ Can feel dated, heavier bundle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scalar - The modern contender&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Beautiful UI, faster, better DX&lt;/li&gt;
&lt;li&gt;✅ Embracing by major frameworks (.NET's new default)&lt;/li&gt;
&lt;li&gt;❌ Less ecosystem maturity&lt;/li&gt;
&lt;/ul&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%2F1t7m9ewo51m3u0djmxvg.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%2F1t7m9ewo51m3u0djmxvg.png" alt="Swagger UI documentation" width="800" height="450"&gt;&lt;/a&gt;&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%2F79my6ubk9kg3aw3t3fpw.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%2F79my6ubk9kg3aw3t3fpw.png" alt="Scalar documentation" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⭐ Bonus: Frontend Implementation
&lt;/h2&gt;

&lt;p&gt;Now you might be wondering, how does this actually work on the frontend?&lt;/p&gt;

&lt;p&gt;Well, it’s not difficult.&lt;/p&gt;

&lt;p&gt;Let me introduce you to &lt;a href="https://openapi-ts.dev/" rel="noopener noreferrer"&gt;OpenAPI TypeScript&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine if your axios or fetch client had complete type safety and could interact with your API without manual guesswork. That’s exactly the problem OpenAPI TypeScript solves. All you need is the OpenAPI specification, which can be from your backend or a remote URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Getting Started with OpenAPI TypeScript
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install the tool
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; openapi-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Generate your types&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;From a local schema:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx openapi-typescript ./path/to/my/schema.yaml &lt;span class="nt"&gt;-o&lt;/span&gt; ./path/to/my/schema.d.ts
&lt;span class="c"&gt;# 🚀 ./path/to/my/schema.yaml -&amp;gt; ./path/to/my/schema.d.ts [7ms]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;From a remote schema:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx openapi-typescript https://myapi.dev/api/v1/openapi.yaml &lt;span class="nt"&gt;-o&lt;/span&gt; ./path/to/my/schema.d.ts
&lt;span class="c"&gt;# 🚀 https://myapi.dev/api/v1/openapi.yaml -&amp;gt; ./path/to/my/schema.d.ts [250ms]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can import types and contracts directly from your schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./my-openapi-3-schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Schema object&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MyType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schemas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Path parameters&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EndpointParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/my/endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parameters&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Response objects&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SuccessResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/my/endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;responses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ErrorResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/my/endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;responses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wait, what about type-safe API clients?
&lt;/h2&gt;

&lt;p&gt;Even with generated types, you’d still need to manually wire up your API calls!&lt;/p&gt;

&lt;p&gt;That’s where &lt;a href="https://openapi-ts.dev/openapi-fetch/" rel="noopener noreferrer"&gt;OpenAPI Fetch&lt;/a&gt; comes in.&lt;/p&gt;

&lt;p&gt;It gives you a fully type-safe client. No more guessing and no more duplicated logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Type-Safe API Calls
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openapi-fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./my-openapi-3-schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// generated by openapi-typescript&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://myapi.dev/v1/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// only present if 2XX response&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// only present if 4XX or 5XX response&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blogposts/{post_id}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PUT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blogposts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My New Post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This can also be implemented seamlessly with React Query, SWR or you favorite data fetching/mutation library.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: The Black-Box
&lt;/h2&gt;

&lt;p&gt;Please, always remember this: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your frontend application must treat the backend as a black box.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It does not care about your database schema, crypto helpers, or specific implementation details. It only sees the API and the public interfaces.&lt;/p&gt;

&lt;p&gt;By forcing your entire system to rely on a generated, language-agnostic OpenAPI Contract, you achieve true separation. You replace the questionable monorepo setup with an actual handshake.&lt;/p&gt;

&lt;p&gt;This is the difference between a codebase that scales and a codebase that burns you out. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop debugging what you didn't write and start building what you control.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Join Escaping From the Abyss
&lt;/h2&gt;

&lt;p&gt;Thank you so much for reading! :)&lt;/p&gt;

&lt;p&gt;I’m launching a newsletter where I share hard-earned lessons from the trenches of startup engineering, especially in backend development and product planning.&lt;/p&gt;

&lt;p&gt;I’ve seen engineers suffer from broken workflows and scope creep. I’ve even lived it. And I believe every dev deserves clarity, autonomy, and the tools to build well; both technically and emotionally.&lt;/p&gt;

&lt;p&gt;This isn’t just about clean code. It’s about clean contracts, clean boundaries, and clean teams.&lt;/p&gt;

&lt;p&gt;Let’s make startups a better place, through better engineering, better business practices, and better mentorship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://newsletter.itsfranciscoluna.com/" rel="noopener noreferrer"&gt;Join me here!&lt;/a&gt; Let’s escape from the abyss.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Lessons Learned as a 21 YO CTO</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Sat, 18 Oct 2025 15:20:55 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/i-was-a-21-year-old-cto-heres-why-i-walked-away-2eci</link>
      <guid>https://dev.to/franciscolunadev82/i-was-a-21-year-old-cto-heres-why-i-walked-away-2eci</guid>
      <description>&lt;p&gt;The truth is, the greatest architectural failure was not in code, but in my career path.&lt;/p&gt;

&lt;p&gt;I carried the title of CTO (Chief Technology Officer) at 21. It sounds great, because you're able to select the technologies a company uses, you're an executive and you manage client relationships.  &lt;/p&gt;

&lt;p&gt;But internally, my CPU was at 100%. I was the solo founding engineer sustaining the product. I had to be a generalist in every single domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend&lt;/li&gt;
&lt;li&gt;Database Optimization&lt;/li&gt;
&lt;li&gt;DevOps&lt;/li&gt;
&lt;li&gt;Backend &lt;/li&gt;
&lt;li&gt;Client PM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The context-switching, multi-tasking and lack of proper management experience or a technical team turned the position into a bottleneck. The problem was never the position. The problem were the conditions and responsibilities that came with it and I was not prepared for.&lt;/p&gt;

&lt;p&gt;I'm still coding and delivering results, but I'm no longer the former CTO I used to be. &lt;/p&gt;

&lt;p&gt;Let me share with you the 5 most important lessons I could've learned during this journey of almost 1 year.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Map your capabilities to your aspirations
&lt;/h2&gt;

&lt;p&gt;Before you can build anything great, you must survey the land. I failed to do this.&lt;/p&gt;

&lt;p&gt;I was too focused on the business results and outputs (features, deadlines, metrics, etc) that I was neglecting my own skills. I didn't know IaC (Infrastructure as Code), proper Networking or scalable Systems Design. The "CTO" role was nothing but a mask to hide those gaps from others, and more dangerously, for myself.&lt;/p&gt;

&lt;p&gt;Your career is a system that needs maintainance. Audit it regularly. Be  honest about the gap between your current skills and your future goals.&lt;/p&gt;

&lt;p&gt;Think of it like climbing a mountain. The title is the summit.&lt;/p&gt;

&lt;p&gt;Most people believe you have two choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bare Hands: The brutal and manual grind. You fight for every inch of ground through sheer force and overtime.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Helicopter: The smart, strategic path using the right tools and skills.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This is a lie.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The truth is, you use your bare hands to build the helicopter. I was so exhausted from the manual labor of firefighting and solo-coding that I never stopped to actually build the vehicle that could lift us to new heights. I was stuck in permanent manual mode, confusing hard work for smart progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't just climb with your hands. Build the damn helicopter.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Specialize to scale
&lt;/h2&gt;

&lt;p&gt;Before we talk about specialization, let me ask you something more revealing than what programming languages you know: What are your hobbies?&lt;/p&gt;

&lt;p&gt;Mine is music composition. I'm obsessed with it. In music, you don't just throw notes together. You use music theory, a structured system of rules and patterns. You make changes through careful voice leading, ensuring each note moves smoothly to the next. You work within constraints like scales and tempo to create something beautiful and coherent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I realized I don't just love music; I love the architecture of music.&lt;/strong&gt; (I'm a music theory nerd, too. 🤣)&lt;/p&gt;

&lt;p&gt;And that's why I'm specializing in backend and cloud engineering. The backend is the music theory of software. It's the hidden structure that makes everything else possible. Cloud engineering is the orchestration, ensuring every service is in tune and in time.&lt;/p&gt;

&lt;p&gt;This doesn't mean I can't do frontend. I can. Just like a composer can learn to paint, it's a different skill. But I'd rather be the one writing the symphony than designing the concert poster. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I want to build systems that scale and grow without me.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Specialization isn't about closing doors; it's about mastering the architecture you're truly passionate about building. It’s the difference between playing every instrument and becoming a virtuoso of the one that speaks to your soul.&lt;/p&gt;

&lt;p&gt;For me, that’s the symphony of systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Growth over titles
&lt;/h2&gt;

&lt;p&gt;Let's be honest. You're a 21-year-old CTO at a startup. It sounds impressive.&lt;/p&gt;

&lt;p&gt;Now imagine you have to sit down and negotiate with other CTOs. They're discussing microservices, Kubernetes, CEO negotiations, hiring strategies, scaling to millions, and complex systems design on AWS.&lt;/p&gt;

&lt;p&gt;And it all sounds like buzzwords to you.&lt;/p&gt;

&lt;p&gt;This exact scenario is why I quit.&lt;/p&gt;

&lt;p&gt;Not because I was scared, but because I was strategic. I saw two paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Ego Path: Stay in the room, fake it, and slowly be exposed as inexperienced while my actual technical skills rust.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Growth Path: Leave the room, admit what I didn't know, and go build the skills to earn my way back in authentically.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you're starting out, you must prioritize your growth, not your title. It's great to learn from other CTOs, but not at the expense of your technical foundation.&lt;/p&gt;

&lt;p&gt;You're better off monetizing a SaaS, being a freelancer, or excelling as an engineer at a company where you can learn new tech.&lt;/p&gt;

&lt;p&gt;Specialize. Grow. Learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't try to be a pro F1 driver when you're still learning to drive a kart. Get proper training first.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Find a team and mentors
&lt;/h2&gt;

&lt;p&gt;I'm done being the solo engineer.&lt;/p&gt;

&lt;p&gt;I'm done guessing. Done sending pull requests into a void, with no one to question my decisions or suggest a better way. Done being unaware of the teamwork and best practices that transform good code into great systems. This isolation probably even stunted my Git skills. When you only answer to yourself, you never learn the true power of collaboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't be the only technical person in the room unless it's your own venture.&lt;/strong&gt; Even then, it's a temporary state, not a sustainable strategy. Solitude is a career liability that only limits your knowledge.&lt;/p&gt;

&lt;p&gt;That's why I'm joining a team at work. To immerse myself in code reviews, architectural debates, and shared ownership. I'm also studying &lt;em&gt;Web Scalability for Startup Engineers&lt;/em&gt;. This is the book I wish I'd read before writing a single line of professional code.&lt;/p&gt;

&lt;p&gt;It's the book that would have taught me that &lt;strong&gt;scaling isn't a feature you add later; it's a property you design for from the beginning.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's never too late to stop being a one-man band and learn how to play in an orchestra.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Burnout and mental health
&lt;/h2&gt;

&lt;p&gt;Protect your mental health, relationships, and happiness at all costs. No equity or title can buy your freedom.&lt;/p&gt;

&lt;p&gt;I'm focused on growing and learning now, not managing. Rest. Accept that it's okay to not know everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The moment your heart stops choosing something, even though your mind has rational excuses, it's no longer for you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whether it causes trauma, breaks you, or holds you back; let it go. Your most important system is the life you build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A lot of people say I need therapy.&lt;/p&gt;

&lt;p&gt;They misunderstand. My choice to leave wasn't a cry for help, but a declaration of independence.&lt;/p&gt;

&lt;p&gt;Look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Therapy doesn't build resilient systems at 3 AM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Therapy doesn't master cloud infrastructure or elegant code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Therapy doesn't deliver the profound satisfaction of creation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found my therapy in building. In the logic of systems, the flow of music, and the confidence that comes from genuine mastery.&lt;/p&gt;

&lt;p&gt;They see a former CTO. I see a builder who finally learned that the ultimate scale is a life of freedom, surrounded by family and friends, doing work that matters; on my own terms.&lt;/p&gt;

&lt;p&gt;The title was a cage. The freedom is the system I'm building now.&lt;/p&gt;

&lt;p&gt;(In case you're wondering, I didn't quit. I'm a Backend and DevOps Engineer at the company now.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, what do you actually want?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not what your resume wants. Not what your ego wants. Not what other people think you should want.&lt;/p&gt;

&lt;p&gt;I know what I want. I want to build apps. I want to be with my people. I want to deepen my backend and cloud engineering skills. I want to contribute my knowledge, mentor others, educate myself and own my time.&lt;/p&gt;

&lt;p&gt;I want that more than a place on a cap table. More than chasing investors. More than a fancy title that costs me my freedom.&lt;/p&gt;

&lt;p&gt;The title was a lesson. The building is the reward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now it's your turn. What do you actually want? Share your "Path" in the comments.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a complex application or a high-performance system?
&lt;/h3&gt;

&lt;p&gt;I help startups engineer scalable applications and internal tools that stay fast as they grow. See how I build scalable systems here: 👉 &lt;a href="//www.itsfranciscoluna.com"&gt;www.itsfranciscoluna.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
      <category>programming</category>
    </item>
    <item>
      <title>10 Database Design Lessons Learned Working for a Startup</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Sun, 31 Aug 2025 14:48:45 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/10-database-design-lessons-learned-working-for-a-startup-446</link>
      <guid>https://dev.to/franciscolunadev82/10-database-design-lessons-learned-working-for-a-startup-446</guid>
      <description>&lt;p&gt;Hi! The abyss has been quite intense these weeks, but we're back with a new entry. I remember when I was working in the database design of my first position a year and a half ago. The amount of things I was blundering: bad practices, poor naming conventions... I guess that's how we all start and learn at the beginning.&lt;/p&gt;

&lt;p&gt;I've been working on database design at a startup, and this time, it feels like the system is no longer against me. It's all about best practices, scalability, and clear conventions. But this didn't happen suddenly; it took countless mistakes, hours, and pain.&lt;/p&gt;

&lt;p&gt;I've gone through the process of learning the best practices on my own and ensuring my schemas can actually scale, so you don't have to go through the same painful process. Here are the &lt;strong&gt;10 database design lessons&lt;/strong&gt; I learned the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Avoid overusing enums
&lt;/h2&gt;

&lt;p&gt;An &lt;code&gt;enum&lt;/code&gt; (enumeration) is a data type that lets you define a list of possible string values a column can hold. The database then enforces that the data inserted into that column must be one of those values. They are great for small, unchanging sets of values but they're a pain to update because it requires a schema migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lookup tables&lt;/strong&gt; are separate tables that contain a list of values, and your main table references them using a foreign key. This makes the values easy to manage, add, or remove without changing the main table's schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple&lt;/strong&gt; &lt;code&gt;Enum&lt;/code&gt; in PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;user_status_enum&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;ENUM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inactive'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'suspended'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;user_status_enum&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scalable Lookup Table Schema:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;post_categories&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;category_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;post_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backend Dictionaries
&lt;/h3&gt;

&lt;p&gt;These are data structures in your application code that hold a list of valid values for a specific field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This data structure is dynamically populated from the database&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CategoryDictionary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;categoryName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CATEGORIES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CategoryDictionary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tech&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Music&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Travel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// Example usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;categoryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CATEGORIES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tech&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// Returns 1&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValidCategory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;CATEGORIES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;categoryName&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// Checks if a category exists&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While they can be a useful tool for validation, simply using a static array of strings in the database isn't a robust solution on its own. It directly &lt;strong&gt;violates the principles of database normalization&lt;/strong&gt; by creating a redundancy between your application and your database schema.&lt;/p&gt;

&lt;p&gt;If the list of valid values changes, you have to update your code &lt;strong&gt;and&lt;/strong&gt; potentially your database, leading to a disconnect between the two sources of truth. A more normalized approach is to use these dictionaries alongside a &lt;strong&gt;lookup table&lt;/strong&gt;. The dictionary in your backend can be populated from the lookup table in your database on application startup. This way, the database remains the single source of truth for the list of values, and your application always has the most up-to-date data for validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Design for the Business, Not for Convenience
&lt;/h2&gt;

&lt;p&gt;This principle is about creating a database schema that accurately models the real-world business concepts, rather than what's easiest for your code. A &lt;code&gt;DATE&lt;/code&gt; data type, for instance, represents a full calendar date. While a developer might find it convenient to store a birthday as three separate integers for year, month, and day, the business understands a birthday as a single date. Using the correct data type ensures your database reflects the truth of the business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad Design:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;user_birthdays&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;birth_month&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;birth_day&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;birth_year&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good Design:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;birthday&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Consistency Matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Consistency&lt;/strong&gt; means using the same naming conventions, data types, and structural patterns throughout your entire database. For example, if you choose &lt;code&gt;snake_case&lt;/code&gt; (e.g., &lt;code&gt;user_id&lt;/code&gt;), you should use it everywhere. Inconsistent naming like &lt;code&gt;user_id&lt;/code&gt; in one table and &lt;code&gt;userId&lt;/code&gt; in another leads to confusion and bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inconsistent Naming (Bad):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;user_posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;post_id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Consistent Naming (Good):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;user_posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Everything Should Serve a Purpose
&lt;/h2&gt;

&lt;p&gt;Every table, column, and index in your database should exist for a specific, current business requirement. &lt;strong&gt;Over-engineering&lt;/strong&gt; is the act of building features or adding complexity that isn't currently needed, often with the thought of "just in case." This adds unnecessary complexity and can harm performance. The goal is to keep the database as simple as possible while meeting all requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-engineered (Bad):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;shipping_notes&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;supplier_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;is_on_sale&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sale_price&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your business only has one supplier and products are never on sale, these columns are just clutter. &lt;strong&gt;Don't try to cover all possible future cases when a feature hasn't even been requested yet.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Don't Let the Database Handle Authentication
&lt;/h2&gt;

&lt;p&gt;Your database is a data store, not a security server. Don't rely on features like PostgreSQL's &lt;strong&gt;Row-Level Security (RLS)&lt;/strong&gt; for core authentication, please. It sounds easy to do, but youre introducing provider-lock in. What if you change the database youre working with in the future? Thats why all authentication and password hashing should be handled in your application layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example RLS Policy (to show what to avoid):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Now your app's security is coupled to the DB...&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;user_access&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;user_posts&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.user_id'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. The Database Shouldn't Handle Business Logic Validations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt; is the process of ensuring data is correct and meaningful. The database should only perform basic validations like ensuring a field is not null or that a value is unique. Complex business logic, like checking if a number is between 1 and 12 for a month, should be handled by your application code. This separation of concerns keeps your database clean and your application flexible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application-level validation (Better):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your backend code&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Error handling&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Separate Staging and Production Databases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Production&lt;/strong&gt; is the public-facing environment. &lt;strong&gt;Staging&lt;/strong&gt; is a pre-production environment used to test new features. You should never work directly on your production database. Mistakes are inevitable. Having separate databases creates a safety net, so you don't accidentally delete real customer data. I recommend you using a product like &lt;strong&gt;Hashicorp Vault&lt;/strong&gt; to properly save the credentials and URLs to work with these environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Never Take Scalability for Granted
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt; is the ability of a system to handle a growing amount of work. Its a mindset you must adopt from the beginning. A key part of this is understanding the impact of your design choices. An &lt;strong&gt;index&lt;/strong&gt; is a data structure that improves the speed of data retrieval operations on a database table. Forgetting to add them to foreign keys is a common mistake that can lead to major performance issues as your data grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding an Index
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;user_posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_user_posts_user_id&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;user_posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Think About the Long Term
&lt;/h3&gt;

&lt;p&gt;Always build your tables with the expectation that there will be changes and new features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler:&lt;/strong&gt; There will always be changes.&lt;/p&gt;

&lt;p&gt;This means you should design for &lt;strong&gt;the long term&lt;/strong&gt;. A critical part of this is adding a new column. To avoid database conflicts during a migration, a new column &lt;strong&gt;must be nullable or have a default value&lt;/strong&gt;. This prevents live applications from failing when they try to insert new data. While a nullable column is a common and necessary workaround, using a default value is often a better practice from a data integrity standpoint, as it avoids nulls and maintains a cleaner dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document Your Schemas
&lt;/h3&gt;

&lt;p&gt;In a startup, your data structures will evolve quickly. When using flexible data types like &lt;code&gt;JSON&lt;/code&gt; or &lt;code&gt;JSONB&lt;/code&gt; to store semi-structured data, it's easy to lose track of the schema. That's why you should &lt;strong&gt;document your JSON/JSONB schemas and structures through a formal specification like OpenAPI&lt;/strong&gt;. This ensures that your entire team and any future consumers of your API understand the data format, preventing errors and ensuring consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Use the Right Data Structures
&lt;/h2&gt;

&lt;p&gt;Choosing the correct data type (e.g., &lt;code&gt;BOOLEAN&lt;/code&gt;, &lt;code&gt;DATE&lt;/code&gt;, &lt;code&gt;JSONB&lt;/code&gt;) for a column is important for performance and clarity. &lt;code&gt;JSONB&lt;/code&gt; is a PostgreSQL data type that stores JSON data in a binary format which allows you to index and query it efficiently. Its perfect for semi-structured data where the schema might be flexible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example with&lt;/strong&gt; &lt;code&gt;JSONB&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;social_media_posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;social_media_posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'{"tags": ["newmusic"], "likes": 150}'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  10. Always Choose UUIDs Over &lt;code&gt;auto_increment&lt;/code&gt; IDs
&lt;/h2&gt;

&lt;p&gt;An &lt;code&gt;auto_increment&lt;/code&gt; ID is a simple integer that automatically increases with each new row. A &lt;strong&gt;UUID (Universally Unique Identifier)&lt;/strong&gt; is a 128-bit number that is globally unique. UUIDs are essential for distributed systems because they can be generated anywhere, on a client, in a microservice; without a central authority to ensure uniqueness. This prevents ID conflicts and makes your system much more scalable and secure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why UUIDs are better:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No Conflicts in Distributed Systems:&lt;/strong&gt; With &lt;code&gt;auto_increment&lt;/code&gt;, two different services inserting data at the same time might generate the same ID. UUIDs solve this problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security &amp;amp; Data Leaks:&lt;/strong&gt; Predictable, sequential IDs like &lt;code&gt;1, 2, 3...&lt;/code&gt; are a major security risk. A malicious user can simply try incrementing IDs in an API endpoint (e.g., &lt;code&gt;/api/users/1&lt;/code&gt;, then &lt;code&gt;/api/users/2&lt;/code&gt;) to scrape or access other users' data. With UUIDs, the IDs are random and impossible to guess, protecting against these types of attacks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example in PostgreSQL:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- The UUID is generated automatically&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'janedoe@example.com'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Database design is a brutal teacher in software engineering. The most painful lessons aren't in a tutorial when you're developing; they're in production, where a small mistake can become a colossal monster. Don't be discouraged by your blunders or failures as they're the very thing that forges your mastery.&lt;/p&gt;

&lt;p&gt;Think of it like Dark Souls. You dont get good by avoiding the bosses; you get good by facing them, learning their patterns, and finally beating them. The ten lessons in this post are your strategies. Now you're ready to face your own production monsters. I know I'll be facing more challenges, and I'm willing to keep sharing with you everything I learn on this journey.&lt;/p&gt;

&lt;p&gt;See you in the next entry. Dont let the abyss consume you.&lt;/p&gt;

</description>
      <category>database</category>
      <category>backend</category>
      <category>javascript</category>
      <category>sql</category>
    </item>
    <item>
      <title>Uploading Images to AWS S3 with Next.js and React Dropzone — A Complete Guide</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Fri, 27 Sep 2024 01:41:16 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/uploading-images-to-aws-s3-with-nextjs-and-react-dropzone-a-complete-guide-4a0h</link>
      <guid>https://dev.to/franciscolunadev82/uploading-images-to-aws-s3-with-nextjs-and-react-dropzone-a-complete-guide-4a0h</guid>
      <description>&lt;p&gt;I’ve been learning how to set up AWS S3 with Next.js at work for a few weeks. At first, it felt a bit overwhelming with different approaches and configurations to consider. After a while, this task got easier and I integrated S3 to my tech stack.   &lt;/p&gt;

&lt;p&gt;Adding S3 can boost your project's functionality and performance, especially when you need to work with images and files in your application. That's why In this guide, I'll take you step-by-step through setting up S3 with Next.js. We'll create a small application where users will be able to add images from their devices through a drop zone component, to be uploaded to S3.  &lt;/p&gt;

&lt;h2&gt;
  
  
  What is AWS S3?
&lt;/h2&gt;

&lt;p&gt;Amazon S3 or Simple Storage Service is one of the core services of Amazon Web Services. It gives you a highly scalable, reliable and secure object storage service on the cloud. &lt;/p&gt;

&lt;p&gt;S3 offers developers and businesses a highly reliable and cost-effective solution for storing and retrieving unlimited amounts of data, accessible anytime and from anywhere on the web.&lt;/p&gt;

&lt;p&gt;And here's an interesting tidbit: the platform you're reading this on, Dev.to, uses Amazon S3 to store all the blog images! It's a perfect real-world example of how S3 is seamlessly integrated into the digital experiences we use every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;Before diving into this guide, it’s essential to have a foundational understanding of the following technologies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Next.js:&lt;/strong&gt; Familiarize yourself with the basics of Next.js, a React framework that enables server-side rendering and static site generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. React:&lt;/strong&gt; Ensure you have a good grasp of React, as Next.js is built on top of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. AWS IAM User:&lt;/strong&gt; Set up an AWS Identity and Access Management (IAM) user with the necessary permissions to access and manage your S3 bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. S3 Bucket:&lt;/strong&gt; Create and configure an Amazon S3 bucket to store your files. Make sure to note down the access key and secret key for your IAM user, and configure the bucket’s permissions and CORS settings as needed. Save the bucket name as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll be building
&lt;/h2&gt;

&lt;p&gt;In this tutorial you'll be building a simple application that allows users to upload images to S3 thanks to a dropzone component. The images will be retrieved programmatically from a S3 bucket and the users can also delete each image.&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%2F5zm1igfivcrdzro96xw2.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%2F5zm1igfivcrdzro96xw2.png" alt="Simple application made using Next.js, Server Actions, AWS S3 and React-Dropzone" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will teach you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How to use Next.js Server Actions&lt;/strong&gt;: Learn how to use serverless functions to leverage Next.js latest features and best practices to do form submissions seamlessly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How to use the AWS S3 SDK for Node.js&lt;/strong&gt;: You'll learn how to upload images, retrieve them and how to delete images programmatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integrate React Dropzone into your application&lt;/strong&gt;: You're going to add React Dropzone with validations, type safety, hooks and constants to use best practices and ensure a nice user experience. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Installing and Configuring Next.js
&lt;/h2&gt;

&lt;p&gt;To install Next.js, you can use the following command: &lt;code&gt;npx create-next-app@latest&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/docs/getting-started/installation" rel="noopener noreferrer"&gt;Learn more on the official docs.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up dependencies
&lt;/h3&gt;

&lt;p&gt;Once you have installed Next.js, you'll need to install React Drop Zone and the AWS S3 SDK for Node.js.&lt;/p&gt;

&lt;p&gt;To install these dependencies, you can use the following command: &lt;code&gt;npm install react-dropzone @aws-sdk/client-s3&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@aws-sdk/client-s3" rel="noopener noreferrer"&gt;Learn more about the AWS S3 SDK here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/react-dropzone" rel="noopener noreferrer"&gt;Learn more about React Drop zone here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Server Actions
&lt;/h2&gt;

&lt;p&gt;Before building the user interface focus on creating the business logic through server actions. They're the same as the &lt;code&gt;lambda&lt;/code&gt; functions from AWS and you can use them for form submissions.&lt;/p&gt;

&lt;p&gt;For this guide, you'll need to create 2 server actions; One to upload an image to &lt;code&gt;S3&lt;/code&gt; and another one to delete an image.&lt;/p&gt;

&lt;p&gt;Create a new folder called &lt;code&gt;actions&lt;/code&gt; inside your &lt;code&gt;src&lt;/code&gt; folder and start creating your first server action there:&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Action to Upload an Image to a Bucket
&lt;/h3&gt;

&lt;p&gt;This server action is used to upload images to an S3 bucket. It takes two arguments: &lt;code&gt;formData&lt;/code&gt; and &lt;code&gt;payload&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;formData:&lt;/strong&gt; Contains the image data sent from the frontend.&lt;br&gt;
&lt;strong&gt;payload:&lt;/strong&gt; An object with two fields, &lt;code&gt;bucket&lt;/code&gt; and &lt;code&gt;key&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Payload Fields
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;bucket:&lt;/strong&gt; The name of your S3 bucket.&lt;br&gt;
&lt;strong&gt;key:&lt;/strong&gt; A unique identifier for each image in the bucket.&lt;/p&gt;

&lt;p&gt;Every image in S3 needs a &lt;strong&gt;unique&lt;/strong&gt; key to ensure that each file is stored separately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /src/actions/s3.ts&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UploadImageToS3Payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadImageToS3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UploadImageToS3Payload&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrayBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileUploadParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutObjectCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileUploadParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageParam&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error uploading image to S3:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to upload image to S3.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Going On Here?
&lt;/h3&gt;

&lt;p&gt;Let's break down what's happening in the code step by step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Initializing the S3 client&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;First, we create an instance of the S3 client using the following code. Remember to use environment variables to keep credentials safe as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you're passing the AWS region and the necessary credentials (access key and secret access key) to the &lt;code&gt;S3Client&lt;/code&gt; class. These credentials are typically stored in environment variables for security reasons. The S3Client is the object that allows you to interact with Amazon S3. Giving you the possibility to upload files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Extracting Payload and Files Information&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, you'll need extract the bucket and key from the payload, which are essential for specifying where and how each file will be stored in S3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we gather all the files from the formData object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;formData.getAll("file")&lt;/code&gt; method retrieves all files associated with the "file" field from the form data submitted by the frontend. This array of files will be processed and uploaded to S3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Processing and Uploading Each File&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For each file, we perform the following steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrayBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileUploadParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutObjectCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileUploadParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageParam&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Convert File to Buffer:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We start by converting the file into an ArrayBuffer, which represents the file's binary data. Then, we create a Buffer from this ArrayBuffer. This Buffer is what we'll actually send to S3 as the file's content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prepare Upload Parameters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We define fileUploadParams, an object that specifies the details of the upload, including the S3 bucket name (Bucket), the unique key (Key), the file content (Body), and the file type (ContentType).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upload the File:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The PutObjectCommand is used to create a command for uploading the file to S3.&lt;/p&gt;

&lt;p&gt;Finally, we execute this command using s3.send(imageParam), which uploads the file to the specified bucket with the given key.&lt;/p&gt;

&lt;p&gt;By using Promise.all, we ensure that all files are processed and uploaded in parallel, improving the efficiency of the operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Revalidating the Path&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the files are successfully uploaded, we call revalidatePath("/") to refresh the content or invalidate the cache for the root path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful if your application needs to update the displayed content or reflect the changes made by the file uploads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Error Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If anything goes wrong during the upload process, we catch the error, log it to the console, and throw a new error to ensure the issue is properly handled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error uploading image to S3:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to upload image to S3.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;helps&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;diagnosing&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;ensures&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;gracefully&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="nx"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server Action to Delete an Image from S3
&lt;/h3&gt;

&lt;p&gt;This server action deleteImageFromS3, is designed to delete an image stored in an Amazon S3 bucket. It accepts an object containing the &lt;code&gt;Bucket&lt;/code&gt; name and the &lt;code&gt;Key&lt;/code&gt; of the image to be deleted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;S3ParamsPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deleteImageFromS3&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;S3ParamsPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DeleteObjectCommandOutput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DeleteObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error deleting image from S3:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to delete image from S3.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  What's going on here?
&lt;/h4&gt;

&lt;p&gt;Let's break down what's happening in this server action:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Type Definition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, we define the S3ParamsPayload type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;S3ParamsPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;key&lt;/code&gt; is the unique identifier of the image (name or path) of the image within the S3 bucket and &lt;code&gt;Bucket&lt;/code&gt; refers to the name of the S3 bucket where the image is stored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Initializing the S3 client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We create again the S3 client which is necessary to interact with AWS S3.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Provide the respective region and credentials from your AWS account &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Creating the Delete Command&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The DeleteObjectCommand is an AWS SDK command that specifies the exact file to delete from the specified bucket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DeleteObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Executing the Delete Command&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This line sends the DeleteObjectCommand to AWS S3, which processes the request and deletes the specified image from the bucket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Revalidating the Path&lt;/strong&gt;*&lt;br&gt;
Invalidate the cache for the root path of the application after deleting an image so the changes are reflected in real time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Error Handling&lt;/strong&gt;&lt;br&gt;
Add basic error handling to manage any issues that might arise during the deletion process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error deleting image from S3:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to delete image from S3.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retrieving the Images
&lt;/h3&gt;

&lt;p&gt;Let's create a new folder inside src called services. This folder will be used to perform async operations with external services such as AWS S3. Create a new file called s3.ts and add the following function to get the signed URLs from a bucket. This is the way we can get images using AWS S3 in a secure way.&lt;/p&gt;

&lt;p&gt;Signed URLs are temporary, secure URLs that allow access to objects in an S3 bucket for a limited period of time. By signing the URLs, you control who can access your S3 objects and for how long, without making the bucket or objects publicly accessible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/services/s3.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAllObjectsSignedUrls&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;S3ParamsPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// List all objects in the bucket&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ListObjectsV2Command&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;listObjectsOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ListObjectsV2CommandOutput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;listCommand&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signedUrls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listObjectsOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Contents&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getObjectCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSignedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getObjectCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Filter out any null results (in case an object didn't have a Key for some reason)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;signedUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error retrieving objects from S3:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to retrieve objects from S3.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  What's going on here?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1. Initialize the S3 client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As with the server actions, you need to initialize the S3 client once again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Provide the respective region and credentials from your AWS account&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. List Objects in the S3 Bucket&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We use the ListObjectsV2Command to retrieve all objects in the specified bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ListObjectsV2Command&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listObjectsOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ListObjectsV2CommandOutput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listCommand&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will list all the objects stored in the S3 bucket, returning metadata about each object, including the key and last modified date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Generate Signed URLs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once we have the list of objects, the next step is to generate signed URLs for each object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signedUrls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listObjectsOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Contents&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getObjectCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSignedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getObjectCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've generated temporary urls which grant secure access to the objects stored in S3. In this case, we use the &lt;code&gt;getSignedUrl&lt;/code&gt; function to generate URLs that expire after 3600 seconds (1 hour).&lt;/p&gt;

&lt;p&gt;We also use &lt;code&gt;Promise.all&lt;/code&gt;  to handle all the asynchronous URL generation tasks concurrently which improves performance by reducing the total time needed to process all the objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Filter Out Null Values&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In some cases, an object might not have a key, or the operation might fail for some objects. To ensure that the final result only contains valid entries, we filter out any null values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;signedUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Error Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As with any operation that involves external services, there’s a chance something could go wrong. To handle potential issues gracefully, the function includes error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error retrieving objects from S3:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to retrieve objects from S3.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Drop zone Component
&lt;/h2&gt;

&lt;p&gt;The Dropzone component allows users to drag and drop images for upload. We set it up to handle a maximum of 3 images, each up to 2MB in size. The react-dropzone library is used to create the drag-and-drop area and manage the upload process.&lt;/p&gt;

&lt;p&gt;Here's the code for the Dropzone component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FileError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FileRejection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useDropzone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dropzone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;uploadImageToS3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/app/actions/s3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE_MB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE_BYTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE_MB&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_IMAGE_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Dropzone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typeValidator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;FileError&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE_BYTES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;size-too-large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Image file is larger than &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MAX_FILE_SIZE_MB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;MB.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onDrop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="na"&gt;acceptedFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;rejectedFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FileRejection&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rejectedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`You're trying to upload a file larger than &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MAX_FILE_SIZE_MB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;MB. Please try again.`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;acceptedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadImageToS3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_BUCKET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Files uploaded successfully!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error uploading image to S3:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to upload image to S3. Please try again.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setUploading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getRootProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getInputProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isDragActive&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useDropzone&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;onDrop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;typeValidator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;maxSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MAX_FILE_SIZE_BYTES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MAX_IMAGE_COUNT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
        &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nf"&gt;getRootProps&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;border-2 border-dashed border-slate-200 rounded-lg hover:bg-slate-100/50 cursor-pointer duration-200 p-8 text-center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nf"&gt;getInputProps&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isDragActive&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Drop&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-slate-400&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Drag and drop some files here, or click to select files (up to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MAX_IMAGE_COUNT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; images, max &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MAX_FILE_SIZE_MB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;MB each)`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uploading&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Uploading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;}
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Dropzone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Validation:&lt;/strong&gt; We validate file types and sizes. If a file is too large, an error message is displayed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handling Uploads:&lt;/strong&gt; Files are uploaded using uploadImageToS3, a function that sends files to the S3 bucket via a server action we set up earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feedback&lt;/strong&gt;: We provide feedback to the user, like showing an "Uploading..." message while files are being uploaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Image Container Component
&lt;/h2&gt;

&lt;p&gt;The ImageContainer component displays each uploaded image along with a "Delete" button. This button allows users to delete images from the S3 bucket. &lt;/p&gt;

&lt;p&gt;Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ImageContainer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;keyProp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;keyProp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;relative h-48 w-full&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;
        &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;keyProp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;objectFit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cover&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rounded-lg relative&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
        &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="nf"&gt;deleteImageFromS3&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_BUCKET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keyProp&lt;/span&gt;

          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;absolute bg-red-500 rounded-full left-2 top-2 p-1 px-4 text-sm text-white font-medium hover:bg-red-600 duration-200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Delete&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Image Display:&lt;/strong&gt; Each image is displayed using the next/image component, which optimizes image loading in Next.js.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete Functionality:&lt;/strong&gt; The "Delete" button triggers deleteImageFromS3, a function that removes the image from the S3 bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Page: Page.tsx
&lt;/h2&gt;

&lt;p&gt;Finally, the Page.tsx file ties everything together. It displays the Dropzone component and the images that have been uploaded.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Dropzone&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/Dropzone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAllObjectsSignedUrls&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/services/s3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ImageContainer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/ImageContainer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageUrls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAllObjectsSignedUrls&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_BUCKET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max-w-[800px] m-auto p-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Dropzone&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid grid-cols-3 gap-4 mt-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;imageUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ImageContainer&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;keyProp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;))}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dropzone Component:&lt;/strong&gt; This is where users upload images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Displaying Images:&lt;/strong&gt; After uploading, images are fetched from S3 and displayed using the ImageContainer component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fetching Images:&lt;/strong&gt; We use getAllObjectsSignedUrls to get the URLs of all images stored in the S3 bucket. This function was defined in the services file earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Improve the Code
&lt;/h2&gt;

&lt;p&gt;As this blog post was created for educational purposes and I was relatively new to AWS at the time it was written, in a real-world application, you should implement the following improvements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Singleton Client for the S3 class&lt;/strong&gt;: Instead of instantiating a new S3 client for each operation, you should create a singleton instance of the S3 class. This avoids unnecessary overhead and improves performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More robust Typescript types&lt;/strong&gt;: Enhancing TypeScript types will help prevent potential errors and provide a better developer experience. You can define types for common operations like file upload and metadata handling, ensuring better code quality and maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Enhancements
&lt;/h2&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%2F25nnu1eddqetfx7hxl7n.jpg" 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%2F25nnu1eddqetfx7hxl7n.jpg" alt="Security. Image provided by Unsplash." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IAM Roles &amp;amp; Policies:&lt;/strong&gt; Ensure that the S3 bucket's permissions are correctly set up using IAM roles, allowing only authorized users and services to interact with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server-Side Encryption:&lt;/strong&gt; Consider enabling server-side encryption to ensure your files are encrypted at rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Presigned URLs:&lt;/strong&gt; If your app allows file uploads or downloads from the client, use presigned URLs to provide secure temporary access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image Compression&lt;/strong&gt;: Consider using a library like &lt;code&gt;Sharp&lt;/code&gt; to compress the users' images and save storage and resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Uploading images to AWS S3 with Next.js and React Dropzone offers an efficient and scalable solution for handling media uploads in web applications. This guide covered the basic steps to integrate S3 into your project, including file uploads, error handling, and using Next.js Server Actions. &lt;/p&gt;

&lt;p&gt;By implementing the recommended improvements, such as creating a singleton S3 client, leveraging stronger TypeScript types, and enhancing security, you can ensure your application is both performant and secure in production environments.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and I wish you the best in your AWS and development journey!😊&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>aws</category>
    </item>
    <item>
      <title>Getting started with Tables using Next.js, Tanstack Table and Typescript</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Fri, 17 May 2024 01:58:11 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/getting-start-with-tables-using-nextjs-tanstack-table-and-typescript-2aig</link>
      <guid>https://dev.to/franciscolunadev82/getting-start-with-tables-using-nextjs-tanstack-table-and-typescript-2aig</guid>
      <description>&lt;p&gt;Have you ever wondered how some admin panels handle their users in a efficient and comfortable way? Well, tables have come to the rescue! &lt;/p&gt;

&lt;p&gt;They're an efficient way to display a huge amount of information with a proper user experience and design. However, before you'd need to work with libraries and dependencies such as Material UI table; and this is a good library, the problem is that it's about 300kb! You're delivering all that Javascript to your client which is not efficient. &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%2Fw85sbc1ikp7geqkth105.jpg" 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%2Fw85sbc1ikp7geqkth105.jpg" alt="Rubik cube. Image by Alexander Gray on Unsplash." width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Times have changed and now we have alternatives such as Tanstack Table and Shadcn/UI. Amazing UI libraries that allow us to build intuitive and professional tables easily with Typescript support. &lt;/p&gt;

&lt;p&gt;That's why in this guide you'll be learning how to build tables using Next.js, a modern fullstack framework based on React, Shadcn/UI, an amazing and modern UI library for React and JSONPlaceholder as our mockup data. &lt;/p&gt;

&lt;p&gt;Let's get started!&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%2Fz9g5pvcqg4z9fepo22iu.jpg" 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%2Fz9g5pvcqg4z9fepo22iu.jpg" alt="Code. Image by Ferenc Almasi on Unsplash." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What We'll be Building
&lt;/h2&gt;

&lt;p&gt;We're going to build a table to display users using Shadcn/UI, Next.js, Tanstack Table and JSONPlaceholder. You'll be able to work with real world data and tables after reading this guide with the mentioned technologies to deliver higher quality products and software. &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%2Fbedf2ht4972ubd8bi1un.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%2Fbedf2ht4972ubd8bi1un.png" alt="Final Project" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Requirements
&lt;/h2&gt;

&lt;p&gt;To be able to understand and get the most out of this tutorial you need to have &lt;code&gt;Node.js 18.17&lt;/code&gt; or later installed on your computer, a solid understanding of React and Next.js and how to use third party libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Next.js and Shadcn/UI
&lt;/h2&gt;

&lt;p&gt;We're going to install Next.js using the following command from the official website documentation: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx create-next-app@latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Use a name of your preference to name your project and since we're going to use Typescript and the App Router, we'll have the following setup:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Would you like to use TypeScript? Yes&lt;br&gt;
Would you like to use ESLint? Yes&lt;br&gt;
Would you like to use Tailwind CSS? Yes&lt;br&gt;
Would you like to use src/ directory? Yes&lt;br&gt;
Would you like to use App Router? (recommended) Yes&lt;br&gt;
Would you like to customize the default import alias (@/*) Yes&lt;br&gt;
What import alias would you like configured? @/*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To learn more about the installation process of Next.js, you can check of the official &lt;a href="https://nextjs.org/docs/getting-started/installation" rel="noopener noreferrer"&gt;Next.js documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now once the dependencies have been installed go to the project root folder and install Shadcn/UI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx shadcn-ui@latest init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can select the settings you feel the most comfortable with to set up your &lt;code&gt;components.json&lt;/code&gt; file. There will be your Shadcn/UI setup.&lt;/p&gt;

&lt;p&gt;Learn more about the installation process of Shadcn/UI using Next.js in the &lt;a href="https://ui.shadcn.com/docs/installation/next" rel="noopener noreferrer"&gt;official Shadcn/UI installation documentation with Next.js&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Installing Shadcn/UI data table and Tanstack Table
&lt;/h2&gt;

&lt;p&gt;Now that you've installed Shadcn/UI it's time to install the table component from the library. This component can be extended using Tanstack Table so we'll need to install that dependency as well.&lt;/p&gt;

&lt;p&gt;Install the table component from Shadcn/UI using the following command: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx shadcn-ui@latest add table&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And install Tanstack Table, which uses React Table under the hood and will allow you to add filtering and pagination in the future:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install @tanstack/react-table&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;See the official &lt;a href="https://tanstack.com/table/latest/docs/introduction/docs/introduction" rel="noopener noreferrer"&gt;TanStack Table Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can start by fetching data and creating the Typescript types.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting the data
&lt;/h2&gt;

&lt;p&gt;In a real world application you're going to use actual API endpoints with actual client data. However, in this tutorial we'll be using JSONPlaceholder. &lt;/p&gt;

&lt;p&gt;And what is this service? According to the official website:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JSONPlaceholder is a free online REST API that you can use whenever you need some fake data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this case, we'll be fetching fictional user data using the following endpoint:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://jsonplaceholder.typicode.com/users&lt;/code&gt;&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%2Fqafmp1mjlqkq7kaaunon.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%2Fqafmp1mjlqkq7kaaunon.png" alt="Response from JSONPlaceholder users endpoint" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the data we're getting is huge and you might be tempted to create the types yourself. I wouldn't recommend you to do so, but instead, using a website like Transform Tools. This is a website that'll allow us to convert files to other formats such as JSON to Typescript Types or React Props.&lt;/p&gt;

&lt;p&gt;Copy the response data you're getting from the API and paste it here: &lt;a href="https://transform.tools/json-to-typescript" rel="noopener noreferrer"&gt;https://transform.tools/json-to-typescript&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Create a new file in your &lt;code&gt;src&lt;/code&gt; file called &lt;code&gt;types.ts&lt;/code&gt; and paste the interface from the tool you've used before. You're going to save the types of your project there since this is a small project. Keep in mind you'll need to create and organize different folders for types as your project scales.&lt;/p&gt;

&lt;p&gt;Now after some tweaks in the TS interfaces, your data could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/types.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;
  &lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Company&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;suite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;zipcode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;geo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Geo&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Geo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Company&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;catchPhrase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fetching Data and Creating the Services
&lt;/h2&gt;

&lt;p&gt;Let's get started by creating a new folder in our &lt;code&gt;src&lt;/code&gt; folder called &lt;code&gt;services&lt;/code&gt;. In this folder we're going to create our async functions responsible for fetching data. &lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;index.ts&lt;/code&gt;, since we'll only have a single file here for now. Remember to add the return types explicitly as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/services/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsonplaceholder.typicode.com/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Columns and How to Use them
&lt;/h2&gt;

&lt;p&gt;Once you've created the services, you can define the columns of your table.  Columns are where you define the core of what your table will look like. They define the data that will be displayed, how it will be formatted, sorted and filtered. &lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;columns.tsx&lt;/code&gt;. We're going to display the following fields from the user:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Username&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Phone Number&lt;/li&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hence we can create the columns as the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ColumnDef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tanstack/react-table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ColumnDef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessorKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessorKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessorKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;phone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Phone Number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessorKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening in this code? First, we're creating the column definitions importing the &lt;code&gt;ColumnDef&lt;/code&gt; type definition from Tanstack Table. This is a generic type, so we add the type we want to infer, in this case it's &lt;code&gt;User&lt;/code&gt; from our types.&lt;/p&gt;

&lt;p&gt;We create an array with the object definitions we want to display in our table. Each object definition has the properties &lt;code&gt;accessorKey&lt;/code&gt; and &lt;code&gt;header&lt;/code&gt;. &lt;code&gt;accessorKey&lt;/code&gt; refers to the core object field we'll be using, and &lt;code&gt;header&lt;/code&gt; refers to the property's display header in the table. &lt;/p&gt;

&lt;p&gt;Learn more about the columns definitions in the &lt;a href="https://ui.shadcn.com/docs/components/data-table" rel="noopener noreferrer"&gt;official Shadncn/UI documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Tables and their Purpose
&lt;/h2&gt;

&lt;p&gt;Now it's time to create a reusable &lt;code&gt;&amp;lt;DataTable /&amp;gt;&lt;/code&gt; component to render our table.&lt;/p&gt;

&lt;p&gt;Go to the folder called &lt;code&gt;ui&lt;/code&gt; inside our &lt;code&gt;components&lt;/code&gt; folder in &lt;code&gt;src&lt;/code&gt;. Create a new file called &lt;code&gt;data-table.tsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You're going to use the following code from the official Shadcn/UI documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/ui/data-table.tsx&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ColumnDef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;flexRender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getCoreRowModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useReactTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tanstack/react-table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TableBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TableCell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TableHead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TableHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TableRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/ui/table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;DataTableProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ColumnDef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DataTable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;DataTableProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReactTable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;getCoreRowModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getCoreRowModel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rounded-md border&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TableHeader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHeaderGroups&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;headerGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TableRow&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;headerGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;headerGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TableHead&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPlaceholder&lt;/span&gt;
                      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
                      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;flexRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                          &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columnDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                        &lt;span class="p"&gt;)}&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TableHead&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;})}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TableRow&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="p"&gt;))}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TableHeader&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TableBody&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRowModel&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRowModel&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TableRow&lt;/span&gt;
                &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIsSelected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;selected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getVisibleCells&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TableCell&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;flexRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columnDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TableCell&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="p"&gt;))}&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TableRow&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TableRow&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TableCell&lt;/span&gt; &lt;span class="nx"&gt;colSpan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h-24 text-center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TableCell&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TableRow&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TableBody&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Table&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see how this code works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Imports: The component imports necessary functions and components from &lt;code&gt;@tanstack/react-table&lt;/code&gt; and our table component installed previously &lt;code&gt;@/components/ui/table&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Props: The DataTable component receives two props: &lt;code&gt;columns&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt;. &lt;code&gt;columns&lt;/code&gt; represents an array of column definitions, and &lt;code&gt;data&lt;/code&gt; represents the data to be displayed in the table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;useReactTable Hook: Inside the component, the &lt;code&gt;useReactTable&lt;/code&gt; hook is used to create a table instance. It takes &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;columns&lt;/code&gt;, and &lt;code&gt;getCoreRowModel&lt;/code&gt;as parameters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rendering Header: The component renders the table header by mapping over the header groups obtained from the table instance. For each header group, it maps over the headers and renders a &lt;code&gt;TableHead&lt;/code&gt; component for each header.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rendering Body: The component renders the table body by mapping over the rows obtained from the table instance. For each row, it renders a &lt;code&gt;TableRow&lt;/code&gt; component, setting the data-state attribute based on whether the row is selected. Within each row it maps over the visible cells and renders a TableCell component for each cell.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FlexRender: The &lt;code&gt;flexRender&lt;/code&gt; function is used to conditionally render the content of each header and cell based on the provided context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No Results Message: If there are no rows to display, the component renders a single &lt;code&gt;TableRow&lt;/code&gt; with a &lt;code&gt;TableCell&lt;/code&gt; spanning all columns, displaying a "No results" message.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Displaying the Table in our First Page
&lt;/h2&gt;

&lt;p&gt;Let's use what we've built so far to set up our table in &lt;code&gt;src/app/page.tsx&lt;/code&gt; with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/app/columns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DataTable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/ui/data-table&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;getUsers&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex min-h-screen flex-col items-center justify-between p-24&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DataTable&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code works the following way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Imports: The code imports the columns definition from the &lt;code&gt;@/app/columns&lt;/code&gt; file and the &lt;code&gt;DataTable&lt;/code&gt; component from the &lt;code&gt;@/components/ui/data-table&lt;/code&gt; file. Additionally, it imports the &lt;code&gt;getUsers&lt;/code&gt; function from &lt;code&gt;@/services&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fetching Data: Inside the &lt;code&gt;Home&lt;/code&gt; function, the &lt;code&gt;getUsers&lt;/code&gt; function is called asynchronously to fetch user data. This function  makes an HTTP request to an API endpoint to retrieve the data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rendering: Once the data is fetched, it is passed as props to the &lt;code&gt;DataTable&lt;/code&gt; component along with the columns definition. The &lt;code&gt;DataTable&lt;/code&gt; component renders the table UI based on the provided data and &lt;code&gt;column&lt;/code&gt; definitions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Server side rendering: Next.js SSR is used. Server components allow fetching data directly within the component, enabling server-side rendering of data. In this case, &lt;code&gt;getUsers&lt;/code&gt; is invoked directly within the component to fetch user data.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;If you've followed the steps properly, your final result should look like this:&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%2Farkl5jwvx3mxn3jq60xj.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%2Farkl5jwvx3mxn3jq60xj.png" alt="Final Table" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations to reaching this point! You've learned how to create professional tables using Tanstack Table, Next.js and ShadcnUI. You've also learned how to connect to the JSONPlaceholder API and how to consume an API endpoint. &lt;/p&gt;

&lt;p&gt;You can also implement pagination, filtering or sorting in the future to keep improving your skills with tables in web development. I'll create more advanced guides in the future about this topic. &lt;/p&gt;

&lt;p&gt;Thank you so much for reading this tutorial and I hope you've learned something new!&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a complex application or a high-performance system?
&lt;/h3&gt;

&lt;p&gt;I help startups engineer scalable applications and internal tools that stay fast as they grow. See how I build scalable systems here: 👉 &lt;a href="//www.itsfranciscoluna.com"&gt;www.itsfranciscoluna.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>learning</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Lessons Learned from Building Hobby Explore: My First MVP Journey</title>
      <dc:creator>Francisco Luna</dc:creator>
      <pubDate>Thu, 25 Apr 2024 02:04:00 +0000</pubDate>
      <link>https://dev.to/franciscolunadev82/lessons-learned-from-building-hobby-explore-my-first-mvp-journey-43a6</link>
      <guid>https://dev.to/franciscolunadev82/lessons-learned-from-building-hobby-explore-my-first-mvp-journey-43a6</guid>
      <description>&lt;p&gt;After almost 3 months of hard work and dedication with the final version and another 5 with the previous ones, I've finished my first minimum viable product, Hobby Explore. This is an application that allows you to share your hobbies with other people, and you're also able to see other people's.&lt;/p&gt;

&lt;p&gt;However, this process was far from easy. I spent countless hours building 5 different versions, refactoring code, building features and getting feedback from other developers. &lt;/p&gt;

&lt;p&gt;This helped me to develop both my coding skills and my mindset towards building coding projects. That's why I'm sharing here the most important lessons I learned building this project!&lt;/p&gt;

&lt;h2&gt;
  
  
  First Lesson - Give time to yourself and your ideas
&lt;/h2&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%2F1zsl0ucltww449g2wckv.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%2F1zsl0ucltww449g2wckv.png" alt="Old version of Hobby Explore" width="627" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's say you have a great idea for a SAAS product and you develop its first version in 5 weeks. You don't like it and you think it's not a good idea to finish.&lt;/p&gt;

&lt;p&gt;What if you gave it more time? I built the first version of Hobby Explore in 1 month, and I was thinking the same even after building new versions. It wasn't until I landed my first job and finished other clients' projects that I developed the skills to build the final production version, which I'm really proud of. &lt;/p&gt;

&lt;p&gt;Remember, keep developing your skills and trusting your ideas. You don't know if you're going to make it with one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second Lesson - Experiment and Get Feedback
&lt;/h2&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%2Fm1xmzip9wzayypcv8j3s.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%2Fm1xmzip9wzayypcv8j3s.png" alt="An image of the different versions of the landing page for Hobby Explore" width="522" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me tell you a fun fact. The first 3 versions of Hobby Explore were made to experiment with backend development using Express, Node.js and MongoDB to fetch mockup hobbies from the Bored API. Yes, I wasn't even thinking about creating a Social Media App. It wasn't until I shared the idea with other developers that they asked me to add the functionality to create different hobbies. &lt;/p&gt;

&lt;p&gt;I decided to implement it, and the functionality wasn't the best thing in the world, but hey, at least I built the foundation for the MVP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third Lesson - Accept Opportunities and Listen to Others' Ideas
&lt;/h2&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%2F63xsim8uh11wf2ixnjso.jpg" 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%2F63xsim8uh11wf2ixnjso.jpg" alt="Opportunities" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the ideas for the actual MVP and the core UI/UX design were suggested by a designer. He's called Wayne, and he helped me to plan functionalities such as the tips and the user customization in Hobby Explore. Wayne, if you're reading this, you're amazing, and you did an amazing job! Always have someone who can support you throughout the development of your product. &lt;/p&gt;

&lt;p&gt;If that's not your case, you can ask for feedback from other developers and your friends, and you can also promote the product you've been building on social media. That's also a good opportunity to improve your product as people get to know it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fourth Lesson - Enjoy the journey and improve as you go
&lt;/h2&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%2Fl5q9rzxxbl5rxlm6qy5l.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%2Fl5q9rzxxbl5rxlm6qy5l.png" alt="Coding Hobby Explore" width="800" height="373"&gt;&lt;/a&gt;&lt;br&gt;
Even if you're not able to monetize your product or it wasn't as good as you expected, you must feel proud of yourself. You were able to learn new technologies, overcome challenges and experiment with different functionalities and libraries. &lt;/p&gt;

&lt;p&gt;You've also met new people and you've grown as an individual. If you were able to finish one of your best projects, then you can keep building cool stuff and eventually achieve your goals. Iterate, my friend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fifth Lesson - Plan and structure your projects
&lt;/h2&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%2Fc6fe3yx2nzel0jnxgf04.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%2Fc6fe3yx2nzel0jnxgf04.png" alt="An image of the UI design of Hobby Explore made in Figma" width="565" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I cannot stress enough the importance of planning your projects. From the design to the actual functionalities and data structures. It helps you to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Gain clarity and vision: By planning you understand better what you want to achieve and how you think you're going to achieve it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save time: It saves you time from making mistakes and changing data structures, functions and variables in your code in the future.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Achieve goals: By saving time and making better choices when building your project, you're likely to achieve your goals faster. A goal doesn't need to be finishing the whole product. You can use SMART goals and set goals like creating a UI component or developing an API endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Release higher-quality products: So you save time, you gain clarity, and you've achieved your goals. Now, you can also plan your tests through different edge cases to create higher-quality code and make your application more scalable and easier to maintain. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, I spent hours writing tests for the most important functionality of Hobby Explore, the one that allows you to create hobbies through dropzones. It wasn't easy, and I had to learn React Testing Library and Vitest, but it was worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a MVP from scratch can be an exciting experience where you can improve different skills, meet new individuals and share your product with the world. That's why I shared the 5 most important lessons I learned with my application called Hobby Explore. I've covered topics from planning to creativity, inspiring others to trust their own ideas and build unique apps!&lt;/p&gt;

&lt;p&gt;Thank you so much for reading this blog article! I hope you've learned something new and if you've built a product, let me know some of the main lessons you learned building it in the comments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hobby-explore.vercel.app/" rel="noopener noreferrer"&gt;You can see my project, Hobby Explore, here.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>coding</category>
      <category>buildinpublic</category>
      <category>design</category>
    </item>
  </channel>
</rss>
