<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged backenddevelopment</title>
<link>https://coderlegion.com/tag/backenddevelopment</link>
<description>Powered by Question2Answer</description>
<item>
<title>Entity Framework Core Patterns I Actually Use in Production</title>
<link>https://coderlegion.com/32896/entity-framework-core-patterns-i-actually-use-in-production</link>
<description>&lt;p&gt;There is a gap between knowing how Entity Framework Core works and knowing how to use it in a way that holds up under real conditions. The documentation will teach you the first thing. Production will teach you the second, usually by showing you what happens when you get it wrong.&lt;/p&gt;
&lt;p&gt;This post is not a tutorial. It is not a comprehensive guide to EF Core features. It is a walkthrough of the specific patterns I apply consistently across every .NET project I build, with the reasoning behind each one. The patterns themselves are not exotic. The discipline of applying them deliberately is what separates a codebase that ages well from one that accumulates quiet performance problems and confusing data states.&lt;/p&gt;
&lt;p&gt;If you are past the getting-started stage with EF Core and want to understand how experienced developers think about it in production, this is written for you.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Knowing how EF Core works and knowing how to use it without creating problems at scale are two different skills. Production teaches you the second one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;asnotracking-the-default-you-should-be-questioning&quot;&gt;ASNOTRACKING: THE DEFAULT YOU SHOULD BE QUESTIONING&lt;/h2&gt;
&lt;p&gt;By default, every entity you query through EF Core gets attached to the change tracker. The change tracker watches that entity for modifications and uses those modifications to generate UPDATE statements when you call SaveChanges. This is the feature that makes EF Core convenient for write operations.&lt;/p&gt;
&lt;p&gt;It is also overhead you are paying on every read operation, whether you need it or not.&lt;/p&gt;
&lt;p&gt;For a read-only query, attaching entities to the change tracker gives you nothing. The entity is never going to be modified and saved. The tracking is pure cost: memory allocated to hold the snapshot, CPU cycles spent watching for changes, and a change tracker that grows with every query you execute in the same DbContext lifetime.&lt;/p&gt;
&lt;p&gt;The pattern I apply is deliberate rather than default. Every query that exists purely to return data gets AsNoTracking. Every query that might lead to an update does not.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Read-only: tracking adds no value here
var tasks = await _context.Tasks
    .AsNoTracking()
    .Where(t =&amp;gt; t.AssignedUserId == userId)
    .ToListAsync();

// Write path: tracking is needed
var task = await _context.Tasks
    .FirstOrDefaultAsync(t =&amp;gt; t.Id == id);

if (task != null)
{
    task.StatusId = newStatusId;
    await _context.SaveChangesAsync();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The practical impact becomes visible at scale. A dashboard that loads twenty entities without AsNoTracking is allocating tracking overhead for twenty objects the application will never modify. Multiply that across concurrent users and the cost is real.&lt;/p&gt;
&lt;p&gt;The habit worth building is this: when you write a query, ask immediately whether the result will ever be written back. If the answer is no, add AsNoTracking before you move on. It is a two-second decision that compounds positively across the entire codebase.&lt;/p&gt;
&lt;h2 id=&quot;the-n-plus-1-problem-what-it-is-and-why-it-keeps-happening&quot;&gt;THE N PLUS 1 PROBLEM: WHAT IT IS AND WHY IT KEEPS HAPPENING&lt;/h2&gt;
&lt;p&gt;The N plus 1 problem is one of the most common performance issues in applications built with ORMs, and it is easy to introduce without realising it because the code that causes it looks completely reasonable.&lt;/p&gt;
&lt;p&gt;The pattern goes like this. You query a list of entities. Then, for each entity in that list, you access a navigation property that has not been loaded yet. EF Core executes a separate database query for each entity to load that property. One query to get the list, then one query per item in the list. If the list has fifty items, you have made fifty-one database round trips instead of one or two.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// This looks fine but is dangerous
var projects = await _context.Projects
    .AsNoTracking()
    .ToListAsync();

foreach (var project in projects)
{
    // Each iteration triggers a separate query
    var taskCount = project.Tasks.Count;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fix is to load related data intentionally rather than letting EF Core load it lazily on demand. Include brings related entities into the initial query. Select projects only the fields you need. Both approaches consolidate the data retrieval into a single round trip.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Explicit loading with Include
var projects = await _context.Projects
    .AsNoTracking()
    .Include(p =&amp;gt; p.Tasks)
    .ToListAsync();

// Projection with Select: more efficient when you need specific fields
var projectSummaries = await _context.Projects
    .AsNoTracking()
    .Select(p =&amp;gt; new ProjectSummaryDto
    {
        Id = p.Id,
        Name = p.Name,
        TaskCount = p.Tasks.Count
    })
    .ToListAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The projection approach with Select is generally preferable when you are building read models or DTOs, because it gives the database engine the information it needs to execute an optimal query rather than loading full entity graphs and discarding most of the data in application code.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The N plus 1 problem does not look like a bug. It looks like working code. That is why it keeps shipping to production.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The discipline that prevents N plus 1 problems is reviewing every query that accesses navigation properties and asking whether those properties will be loaded by the time they are accessed. If the answer is uncertain, make it certain by being explicit about what gets loaded and when.&lt;/p&gt;
&lt;h2 id=&quot;soft-deletes-with-lookup-tables&quot;&gt;SOFT DELETES WITH LOOKUP TABLES&lt;/h2&gt;
&lt;p&gt;Hard deletes, where a record is physically removed from the database, are rarely the right choice for production data. They break audit trails, they complicate recovery scenarios, and they create referential integrity problems when other records depend on the deleted entity.&lt;/p&gt;
&lt;p&gt;Soft deletes, where a record is marked as inactive rather than removed, solve these problems. The record stays in the database. Its relationships remain intact. The deletion is reversible. And you have a complete history of every state the record has passed through.&lt;/p&gt;
&lt;p&gt;My approach to soft deletes connects directly to the lookup table convention I use for all status fields. Rather than adding a boolean IsDeleted column, every entity that supports soft deletion has a StatusId that references a shared Statuses lookup table. A status of Active means the record is live. A status of Deleted means it has been soft deleted. A status of Archived means it has been intentionally retired without deletion.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Statuses lookup table
public class Status
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

// Entity with status-based soft delete
public class Task
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public int StatusId { get; set; }
    public Status Status { get; set; } = null!;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The advantage over a boolean IsDeleted column is that the status field carries meaning beyond a binary active or inactive state. You can distinguish between a record that was deleted intentionally, one that was archived, one that is pending review, and one that is in draft, all within the same field, without adding columns or changing the schema.&lt;/p&gt;
&lt;p&gt;Adding a new status is a data operation. You insert a row into the Statuses table. No migration required. No enum update. No redeployment. The application picks it up immediately because it is reading status values from the database rather than from a hardcoded list in code.&lt;/p&gt;
&lt;h3 id=&quot;filtering-deleted-records-consistently&quot;&gt;Filtering deleted records consistently&lt;/h3&gt;
&lt;p&gt;The operational discipline that makes soft deletes work is consistent filtering. Every query that should return active records needs to filter on status. If you add that filter in some places and forget it in others, deleted records surface unexpectedly and the soft delete pattern breaks down.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Always filter by active status on read queries
var activeTasks = await _context.Tasks
    .AsNoTracking()
    .Where(t =&amp;gt; t.StatusId == StatusIds.Active)
    .ToListAsync();

// Soft delete: update status, do not remove the record
var task = await _context.Tasks
    .FirstOrDefaultAsync(t =&amp;gt; t.Id == id);

if (task != null)
{
    task.StatusId = StatusIds.Deleted;
    await _context.SaveChangesAsync();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;EF Core global query filters are worth considering for applications where the filtered entity is accessed in many places. A global query filter defined on the DbContext applies automatically to every query for that entity type, which eliminates the risk of forgetting the filter in a particular repository method.&lt;/p&gt;
&lt;h2 id=&quot;repository-methods-that-match-real-use-cases&quot;&gt;REPOSITORY METHODS THAT MATCH REAL USE CASES&lt;/h2&gt;
&lt;p&gt;The generic repository base covers the standard CRUD operations. In production, most of the interesting work happens in the specific repository interfaces that extend the generic base with query methods that match real application needs.&lt;/p&gt;
&lt;p&gt;The principle I apply is that a repository method should represent a meaningful operation in the domain, not a thin wrapper around a LINQ query. The difference is in how the application code reads.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Thin wrapper: the caller still needs to know too much
var tasks = await _taskRepository.GetAllAsync();
var userTasks = tasks.Where(t =&amp;gt; t.AssignedUserId == userId
    &amp;amp;&amp;amp; t.StatusId == StatusIds.Active).ToList();

// Domain-meaningful method: intention is clear at the call site
var userTasks = await _taskRepository
    .GetActiveTasksByUserAsync(userId);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second version is easier to read, easier to test, and easier to optimise. The filtering happens in the repository, close to the database, rather than in application code after a full table scan. And the method name communicates intent in a way that a chain of LINQ operators does not.&lt;/p&gt;
&lt;p&gt;This matters more as a codebase grows. A repository with well-named methods becomes a vocabulary for talking about the domain. A repository that returns raw IQueryable and lets callers filter arbitrarily becomes a leaky abstraction that is difficult to reason about and impossible to optimise consistently.&lt;/p&gt;
&lt;h2 id=&quot;managing-dbcontext-lifetime&quot;&gt;MANAGING DBCONTEXT LIFETIME&lt;/h2&gt;
&lt;p&gt;DbContext is designed to be a unit of work. It is meant to be created, used for a set of related operations, and disposed. It is not meant to be a long-lived singleton shared across the application.&lt;/p&gt;
&lt;p&gt;In an ASP.NET Core application, the standard pattern is to register DbContext as a scoped service, which means one instance per HTTP request. This is correct for most use cases. Each request gets its own context, its own change tracker, and its own transaction boundary. When the request completes, the context is disposed and any uncommitted changes are discarded.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Program.cs: register DbContext as scoped
builder.Services.AddDbContext&amp;lt;AppDbContext&amp;gt;(options =&amp;gt;
    options.UseSqlServer(connectionString));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problems arise when DbContext escapes the scope it was designed for. Injecting it into a singleton service is the most common mistake. A singleton lives for the lifetime of the application. A scoped DbContext injected into a singleton effectively becomes a singleton itself, with a change tracker that accumulates state across every request the application handles until it is restarted.&lt;/p&gt;
&lt;p&gt;If you need database access in a singleton or a background service, use IDbContextFactory to create a short-lived context for each operation, use it, and dispose it immediately. This gives you the database access you need without the lifetime mismatch that causes subtle and difficult-to-diagnose bugs.&lt;/p&gt;
&lt;h2 id=&quot;the-mindset-behind-the-patterns&quot;&gt;THE MINDSET BEHIND THE PATTERNS&lt;/h2&gt;
&lt;p&gt;What these patterns have in common is that they require you to be deliberate rather than accepting defaults. EF Core defaults are designed to be convenient for getting started. They are not always the right choice for production, and the gap between convenient and correct widens as the application grows and the load increases.&lt;/p&gt;
&lt;p&gt;The developers who use EF Core well in production are not the ones who know the most features. They are the ones who ask the right questions before writing each query: is this data going to be modified? What related data does this operation actually need? What happens to this record when it is deleted? How many database round trips is this operation making?&lt;/p&gt;
&lt;p&gt;Those questions have good answers. Getting into the habit of asking them before writing the code, rather than after a performance problem surfaces, is what makes the difference.&lt;/p&gt;
&lt;h2 id=&quot;closing-thought&quot;&gt;CLOSING THOUGHT&lt;/h2&gt;
&lt;p&gt;EF Core is a powerful tool and a forgiving one. It will let you build something that works without understanding any of what is in this post. The cost of that forgiveness shows up later, in query logs, in memory profiles, and in data states that are difficult to explain. The patterns here are not about using EF Core correctly in an academic sense. They are about using it in a way you will not regret six months into a production system.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32896/entity-framework-core-patterns-i-actually-use-in-production</guid>
<pubDate>Wed, 28 Oct 2026 18:49:25 +0000</pubDate>
</item>
<item>
<title>Why Does a Backend API Become Slow, and How Can We Fix It?</title>
<link>https://coderlegion.com/31915/why-does-a-backend-api-become-slow-and-how-can-we-fix-it</link>
<description>&lt;p&gt;As a backend developer, I spend a lot of time thinking about what happens behind the scenes when someone uses an application. A user clicks a button, submits a form, or requests some information, and the backend has to process that request and return a response.&lt;/p&gt;
&lt;p&gt;Everything looks simple when an application is small. But as the number of users and the amount of data increase, things can change quickly. An API that used to respond almost instantly may suddenly take several seconds to return the same information.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, what actually makes a backend API slow, and how can we fix it?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-problem-an-api-that-takes-too-long-to-respond&quot;&gt;The Problem: An API That Takes Too Long to Respond&lt;/h3&gt;
&lt;p&gt;Imagine you're building an e-commerce application using Java and Spring Boot. The application has an API that returns a list of products.&lt;/p&gt;
&lt;p&gt;At first, the API works perfectly. It fetches product information from the database and returns the result to the frontend.&lt;/p&gt;
&lt;p&gt;But after the application grows, users begin to complain that the product page takes too long to load.&lt;/p&gt;
&lt;p&gt;There could be several reasons behind this problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The database is executing inefficient queries.&lt;/li&gt;
&lt;li&gt;The API is retrieving more data than the frontend needs.&lt;/li&gt;
&lt;li&gt;The application is making unnecessary database calls.&lt;/li&gt;
&lt;li&gt;Multiple operations are being performed when they are not required.&lt;/li&gt;
&lt;li&gt;The server is struggling to handle too many requests at once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first mistake would be to start changing the code without understanding the actual cause.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before optimizing an API, we need to identify where the time is being spent.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-1-measure-the-api-performance&quot;&gt;Step 1: Measure the API Performance&lt;/h3&gt;
&lt;p&gt;The first thing I would do is measure the response time.&lt;/p&gt;
&lt;p&gt;Instead of guessing, I would check application logs, database query execution times, and performance metrics to understand whether the delay comes from the database, application logic, or another part of the system.&lt;/p&gt;
&lt;p&gt;For example, if an API takes 1,500 milliseconds to respond, I would investigate how much time is spent executing the database query and how much is spent processing the result.&lt;/p&gt;
&lt;p&gt;This gives us a starting point.&lt;/p&gt;
&lt;p&gt;In a Spring Boot application, tools such as Spring Boot Actuator and Micrometer can help expose useful application metrics. Database query logging and profiling can also help identify expensive operations.&lt;/p&gt;
&lt;p&gt;The goal is simple: find the bottleneck before trying to fix it.&lt;/p&gt;
&lt;h3 id=&quot;step-2-improve-database-queries&quot;&gt;Step 2: Improve Database Queries&lt;/h3&gt;
&lt;p&gt;One common problem is retrieving unnecessary data.&lt;/p&gt;
&lt;p&gt;Suppose the frontend only needs a product's name, price, and image URL. There is no reason to fetch every column from the database if the application doesn't need them.&lt;/p&gt;
&lt;p&gt;A basic SQL query might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *
FROM products;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A more focused query would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT name, price, image_url
FROM products;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can reduce the amount of data transferred and processed, particularly when the table contains many columns or large fields.&lt;/p&gt;
&lt;p&gt;Indexes can also improve performance when queries frequently filter or search by specific columns. However, indexes should be chosen based on actual query patterns because unnecessary indexes increase storage use and can slow down writes.&lt;/p&gt;
&lt;p&gt;Small database improvements can make a noticeable difference when an API is called frequently.&lt;/p&gt;
&lt;h3 id=&quot;step-3-avoid-unnecessary-database-calls&quot;&gt;Step 3: Avoid Unnecessary Database Calls&lt;/h3&gt;
&lt;p&gt;Another issue I would investigate is whether the application is making too many database queries.&lt;/p&gt;
&lt;p&gt;For example, imagine an API returns 100 products. If the application executes one query to fetch the products and then another query for each product's category, it could make 101 queries instead of a smaller number of well-designed queries.&lt;/p&gt;
&lt;p&gt;This is a common example of the N+1 query problem.&lt;/p&gt;
&lt;p&gt;With Java and Spring Data JPA, the solution might involve a carefully designed fetch strategy, a projection, or a query that retrieves the required information together.&lt;/p&gt;
&lt;p&gt;The right approach depends on the relationships between the entities and the data the endpoint actually needs.&lt;/p&gt;
&lt;p&gt;I would also avoid blindly using eager loading everywhere, because loading too much related data can create a different performance problem.&lt;/p&gt;
&lt;h3 id=&quot;step-4-return-only-the-data-the-client-needs&quot;&gt;Step 4: Return Only the Data the Client Needs&lt;/h3&gt;
&lt;p&gt;A backend API should have a clear responsibility: provide the information the client needs to complete a task.&lt;/p&gt;
&lt;p&gt;Returning thousands of records in a single response is rarely a good choice when the user only needs to view a small portion of them.&lt;/p&gt;
&lt;p&gt;Pagination can help.&lt;/p&gt;
&lt;p&gt;For example, a Spring Data repository can support paginated queries:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Page&amp;lt;Product&amp;gt; findAll(Pageable pageable);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A service or controller can then request a specific page rather than loading every product at once.&lt;/p&gt;
&lt;p&gt;For larger datasets, cursor-based pagination may be more suitable than offset-based pagination, depending on the use case.&lt;/p&gt;
&lt;p&gt;The important thing is to make sure the API remains efficient as the dataset grows.&lt;/p&gt;
&lt;h3 id=&quot;step-5-use-caching-when-it-makes-sense&quot;&gt;Step 5: Use Caching When It Makes Sense&lt;/h3&gt;
&lt;p&gt;Sometimes the same information is requested repeatedly even though it rarely changes.&lt;/p&gt;
&lt;p&gt;Product categories, public configuration, and other relatively stable data may be good candidates for caching.&lt;/p&gt;
&lt;p&gt;Spring provides caching support through annotations such as &lt;code&gt;@Cacheable&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Cacheable(&quot;productCategories&quot;)
public List&amp;lt;Category&amp;gt; getCategories() {
    return categoryRepository.findAll();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the first successful lookup, subsequent calls can reuse the cached result, depending on the configured cache provider and cache rules.&lt;/p&gt;
&lt;p&gt;However, caching is not a solution for every slow API. We need to consider expiration, invalidation, memory usage, and whether users might receive outdated information.&lt;/p&gt;
&lt;p&gt;For frequently changing or sensitive data, caching requires additional care.&lt;/p&gt;
&lt;h3 id=&quot;step-6-test-the-changes&quot;&gt;Step 6: Test the Changes&lt;/h3&gt;
&lt;p&gt;After making improvements, I would test the API again using the same conditions as before.&lt;/p&gt;
&lt;p&gt;I would compare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Average response time.&lt;/li&gt;
&lt;li&gt;Higher-percentile latency, such as p95.&lt;/li&gt;
&lt;li&gt;Database query count and execution time.&lt;/li&gt;
&lt;li&gt;Error rates.&lt;/li&gt;
&lt;li&gt;Resource usage under concurrent requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A change that makes one request faster but causes errors under heavy traffic is not a successful optimization.&lt;/p&gt;
&lt;p&gt;Performance testing tools can help simulate multiple users and reveal problems that may not appear during ordinary development.&lt;/p&gt;
&lt;p&gt;I would also keep automated tests in place to ensure that performance improvements don't accidentally change the API's expected behavior.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned-from-this-approach&quot;&gt;What I Learned From This Approach&lt;/h3&gt;
&lt;p&gt;The biggest lesson is that backend performance is not just about writing shorter code or choosing a faster programming language.&lt;/p&gt;
&lt;p&gt;A well-designed API depends on several things working together: efficient database queries, sensible data retrieval, appropriate caching, reliable application logic, and good monitoring.&lt;/p&gt;
&lt;p&gt;Java and Spring Boot provide useful tools for building APIs, but the framework cannot automatically make every design decision efficient.&lt;/p&gt;
&lt;p&gt;As developers, we still need to understand what the application is doing and why a particular operation is taking too long.&lt;/p&gt;
&lt;p&gt;I would rather spend time identifying the actual bottleneck than introduce unnecessary complexity in the name of optimization.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Building a backend API that works is one challenge. Keeping it reliable and responsive as the application grows is another.&lt;/p&gt;
&lt;p&gt;When an API becomes slow, I think the best approach is to measure first, identify the problem, make a focused change, and test the result.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good backend development is not only about making an application work today. It's about making sure it continues to work well as more users depend on it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'm interested in how other Java developers approach API performance. When you notice a slow endpoint in your application, what's the first thing you investigate: database queries, application logic, or something else?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31915/why-does-a-backend-api-become-slow-and-how-can-we-fix-it</guid>
<pubDate>Wed, 21 Oct 2026 03:36:54 +0000</pubDate>
</item>
<item>
<title>My Journey as a Java Developer: Learning, Building, and Solving Problems Through Code</title>
<link>https://coderlegion.com/30610/my-journey-as-a-java-developer-learning-building-and-solving-problems-through-code</link>
<description>&lt;p&gt;Software development is more than writing code. It is about understanding problems, finding practical solutions, and building applications that make people's work easier. As a Java Developer, I enjoy exploring how software works behind the scenes, improving my programming skills, and turning ideas into useful applications.&lt;/p&gt;
&lt;p&gt;My journey in development is driven by curiosity, continuous learning, and the desire to become better at what I do every day.&lt;/p&gt;
&lt;h3 id=&quot;why-i-chose-java-development&quot;&gt;Why I Chose Java Development&lt;/h3&gt;
&lt;p&gt;Java is one of the technologies that interests me because of its versatility, object-oriented approach, and ability to support reliable and scalable applications. From backend systems to enterprise-level software, Java provides a strong foundation for building applications that can grow and adapt to different requirements.&lt;/p&gt;
&lt;p&gt;What I enjoy most is the process of understanding how a problem works and figuring out how to solve it through code. Sometimes the solution is simple, while other times it requires breaking the problem into smaller parts, testing different approaches, and learning from mistakes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For me, programming is not just about making something work. It is about understanding why it works and finding ways to make it better.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;my-approach-to-writing-code&quot;&gt;My Approach to Writing Code&lt;/h3&gt;
&lt;p&gt;I believe that good software starts with clear thinking and well structured code. Writing code that works is important, but writing code that other developers can understand, maintain, and improve is equally valuable.&lt;/p&gt;
&lt;p&gt;As I continue developing my skills, I focus on several important areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Java programming:&lt;/strong&gt; Understanding core concepts and applying them to practical problems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object-oriented programming:&lt;/strong&gt; Using classes, objects, encapsulation, inheritance, and polymorphism to organize code effectively.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backend development:&lt;/strong&gt; Learning how application logic, databases, and APIs work together to deliver useful functionality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problem-solving:&lt;/strong&gt; Breaking complex challenges into manageable tasks and exploring efficient solutions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean code:&lt;/strong&gt; Writing readable, maintainable code that makes future improvements easier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These areas help me build a stronger foundation and approach development tasks with more confidence.&lt;/p&gt;
&lt;h3 id=&quot;learning-beyond-the-basics&quot;&gt;Learning Beyond the Basics&lt;/h3&gt;
&lt;p&gt;Technology continues to evolve, and I believe developers should remain curious and open to learning. There is always something new to explore, whether it is a better programming technique, a different architectural approach, or a tool that improves the development process.&lt;/p&gt;
&lt;p&gt;I enjoy experimenting with code, exploring new concepts, and learning from both successful projects and mistakes. Debugging is also an important part of this process. When something does not work as expected, I try to understand the root cause instead of simply looking for a quick fix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every bug can teach you something, and every project gives you an opportunity to improve.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I also recognize the importance of understanding the fundamentals before depending too heavily on frameworks or tools. Strong fundamentals make it easier to learn new technologies and adapt to different project requirements.&lt;/p&gt;
&lt;h3 id=&quot;building-reliable-and-scalable-applications&quot;&gt;Building Reliable and Scalable Applications&lt;/h3&gt;
&lt;p&gt;One of my main interests is developing applications that are reliable, efficient, and easy to maintain. A good application should solve the immediate problem while also being flexible enough to support future changes.&lt;/p&gt;
&lt;p&gt;This requires more than writing individual methods or classes. It involves thinking about application structure, data handling, error management, performance, and how different components communicate with each other.&lt;/p&gt;
&lt;p&gt;As a Java Developer, I want to keep strengthening my understanding of backend architecture, database integration, REST APIs, testing, and software design principles. These skills are valuable for building applications that can handle real-world requirements.&lt;/p&gt;
&lt;p&gt;I believe the best way to improve is to put knowledge into practice. Reading documentation and studying examples are useful, but building projects helps turn theoretical concepts into practical experience.&lt;/p&gt;
&lt;h3 id=&quot;challenges-are-part-of-the-process&quot;&gt;Challenges Are Part of the Process&lt;/h3&gt;
&lt;p&gt;Every developer faces challenges. Sometimes a problem seems straightforward until you start implementing the solution. Other times, an unexpected error forces you to rethink your approach.&lt;/p&gt;
&lt;p&gt;I see these situations as opportunities to develop better problem-solving habits. Instead of giving up when something becomes difficult, I try to investigate the issue, test possible solutions, and understand what went wrong.&lt;/p&gt;
&lt;p&gt;This mindset helps me become more patient and systematic when approaching technical problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Becoming a better developer is not about knowing everything. It is about being willing to learn, adapt, and keep improving.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;growing-with-the-developer-community&quot;&gt;Growing With the Developer Community&lt;/h3&gt;
&lt;p&gt;Software development is a continuous learning journey, and the developer community plays an important role in that process. Developers learn from sharing experiences, discussing different approaches, and helping one another overcome challenges.&lt;/p&gt;
&lt;p&gt;I enjoy connecting with people who are passionate about technology, exploring their ideas, and learning how they approach software engineering problems.&lt;/p&gt;
&lt;p&gt;I also believe that sharing knowledge is a useful way to reinforce what we learn. Explaining a concept, documenting a solution, or discussing a coding challenge can help both the writer and the reader gain a better understanding.&lt;/p&gt;
&lt;p&gt;Through platforms like CoderLegion, I hope to share my experiences, exchange ideas, and connect with other developers who enjoy building useful things with technology.&lt;/p&gt;
&lt;h3 id=&quot;my-goals-as-a-java-developer&quot;&gt;My Goals as a Java Developer&lt;/h3&gt;
&lt;p&gt;My goal is to continue growing as a software developer by strengthening my Java knowledge, improving my problem-solving abilities, and gaining more practical experience in building real-world applications.&lt;/p&gt;
&lt;p&gt;I want to explore better software design practices, understand scalable backend systems, and develop solutions that are useful, reliable, and maintainable.&lt;/p&gt;
&lt;p&gt;I know that becoming a skilled developer takes time, practice, and consistency. There will always be new technologies to learn and new challenges to solve, and that is part of what makes this field interesting to me.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;My journey as a Java Developer is built around learning, experimenting, solving problems, and creating better software. I enjoy the challenge of turning ideas into working applications and discovering new ways to write efficient, maintainable code.&lt;/p&gt;
&lt;p&gt;I do not believe growth happens overnight. It comes from consistent effort, practical experience, curiosity, and the willingness to learn from mistakes.&lt;/p&gt;
&lt;p&gt;I look forward to continuing this journey, building meaningful projects, sharing what I learn, and connecting with developers who share the same passion for technology.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Always learning, always building, and always looking for a better way to solve problems.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30610/my-journey-as-a-java-developer-learning-building-and-solving-problems-through-code</guid>
<pubDate>Sat, 10 Oct 2026 14:52:56 +0000</pubDate>
</item>
<item>
<title>Building Reliable Backend Applications with C# and .NET</title>
<link>https://coderlegion.com/29925/building-reliable-backend-applications-with-c-and-net</link>
<description>&lt;p&gt;Backend development is more than writing code that works. For me, it is about building systems that are &lt;strong&gt;reliable, maintainable, secure, and capable of supporting real business needs&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;My main experience is with &lt;strong&gt;C#, .NET, ASP.NET Core, SQL Server, and Entity Framework&lt;/strong&gt;. I enjoy working on backend systems where there are real requirements to understand, databases to design, APIs to build, and performance problems to solve.&lt;/p&gt;
&lt;h4 id=&quot;turning-business-requirements-into-backend-solutions&quot;&gt;Turning Business Requirements into Backend Solutions&lt;/h4&gt;
&lt;p&gt;A typical project starts with understanding what the business actually needs. Before writing code, I try to understand the workflow, the data involved, and how different parts of the application will communicate.&lt;/p&gt;
&lt;p&gt;Once the requirements are clear, I focus on creating a backend structure that is easy to maintain and can grow with the application.&lt;/p&gt;
&lt;p&gt;This usually involves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Designing backend architecture&lt;/li&gt;
&lt;li&gt;Developing APIs with ASP.NET Core&lt;/li&gt;
&lt;li&gt;Working with SQL Server databases&lt;/li&gt;
&lt;li&gt;Creating data models with Entity Framework&lt;/li&gt;
&lt;li&gt;Implementing authentication and authorization&lt;/li&gt;
&lt;li&gt;Writing business logic in C#&lt;/li&gt;
&lt;li&gt;Improving database queries and application performance&lt;/li&gt;
&lt;li&gt;Debugging and maintaining existing applications&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;working-with-databases&quot;&gt;Working with Databases&lt;/h4&gt;
&lt;p&gt;A backend application is only as good as the data layer behind it.&lt;/p&gt;
&lt;p&gt;I have worked extensively with &lt;strong&gt;SQL Server and Entity Framework&lt;/strong&gt;, including database design, relationships, queries, stored procedures, migrations, and performance optimization.&lt;/p&gt;
&lt;p&gt;One thing I have learned is that database performance should not be treated as an afterthought. A poorly designed query or database structure can affect the entire application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Efficient database design and optimized queries can make a huge difference in application performance.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;building-secure-apis&quot;&gt;Building Secure APIs&lt;/h4&gt;
&lt;p&gt;Modern applications often depend heavily on APIs. I focus on creating APIs that are organized, predictable, and secure.&lt;/p&gt;
&lt;p&gt;From handling requests and validation to authentication, authorization, error handling, and database communication, every layer needs to work together properly.&lt;/p&gt;
&lt;p&gt;I also believe that an API should be designed with the consumers in mind. Clear responses, consistent error handling, and proper validation make applications much easier to maintain.&lt;/p&gt;
&lt;h4 id=&quot;performance-and-problem-solving&quot;&gt;Performance and Problem Solving&lt;/h4&gt;
&lt;p&gt;Backend development often involves finding problems that are not immediately obvious.&lt;/p&gt;
&lt;p&gt;An application may work correctly but become slow when the amount of data increases. A database query may work with a few records but become inefficient with thousands or millions of records.&lt;/p&gt;
&lt;p&gt;That is where debugging and performance analysis become important.&lt;/p&gt;
&lt;p&gt;I enjoy investigating these types of problems, finding the root cause, and improving the implementation rather than simply applying a temporary fix.&lt;/p&gt;
&lt;h4 id=&quot;maintaining-existing-applications&quot;&gt;Maintaining Existing Applications&lt;/h4&gt;
&lt;p&gt;Not every project starts from scratch.&lt;/p&gt;
&lt;p&gt;A large part of backend development can involve working with existing applications, understanding someone else's code, fixing bugs, improving functionality, and gradually making the system better.&lt;/p&gt;
&lt;p&gt;This requires patience and the ability to understand existing architecture before making changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good backend development is not always about rewriting code. Sometimes the best solution is improving what already exists.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;continuous-learning&quot;&gt;Continuous Learning&lt;/h4&gt;
&lt;p&gt;Technology keeps changing, so there is always something new to learn.&lt;/p&gt;
&lt;p&gt;Even while working primarily with C# and .NET, I continue exploring better development practices, modern backend architecture, API design, database optimization, and new tools that can improve the development process.&lt;/p&gt;
&lt;p&gt;For me, programming is a continuous process of learning, building, debugging, and improving.&lt;/p&gt;
&lt;p&gt;I enjoy taking a business requirement, breaking it down into smaller technical problems, and eventually turning those ideas into a backend system that people can actually use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Always learning. Always building. Always looking for a better way to solve the problem.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29925/building-reliable-backend-applications-with-c-and-net</guid>
<pubDate>Tue, 06 Oct 2026 02:40:20 +0000</pubDate>
</item>
<item>
<title>AI - Powered Automatic Email Sending System for File Uploads</title>
<link>https://coderlegion.com/26575/ai-powered-automatic-email-sending-system-for-file-uploads</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;In many organizations and educational environments, users frequently upload documents and then need to manually prepare and send emails containing information about those files.&lt;/p&gt;
&lt;p&gt;This repetitive process can be time-consuming and inefficient.&lt;/p&gt;
&lt;p&gt;To address this, I developed an &lt;strong&gt;AI-powered automatic email sending system&lt;/strong&gt; that combines &lt;strong&gt;file uploads, document text extraction, AI-generated email content, and automated email delivery&lt;/strong&gt; into a single workflow.&lt;/p&gt;
&lt;p&gt;The project demonstrates how AI APIs can be integrated into a traditional PHP web application to automate document-based communication.&lt;/p&gt;
&lt;h3 id=&quot;project-overview&quot;&gt;Project Overview&lt;/h3&gt;
&lt;p&gt;The system provides a web-based interface where users can upload documents.&lt;/p&gt;
&lt;p&gt;Once a file is uploaded, the application:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validates the uploaded file.&lt;/li&gt;
&lt;li&gt;Extracts text from the document.&lt;/li&gt;
&lt;li&gt;Sends the extracted content to an AI API.&lt;/li&gt;
&lt;li&gt;Generates professional email content using AI.&lt;/li&gt;
&lt;li&gt;Automatically sends the email using PHPMailer.&lt;/li&gt;
&lt;li&gt;Displays success or error notifications to the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This creates a streamlined workflow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Document Upload
      ↓
File Validation
      ↓
Text Extraction
      ↓
AI Processing
      ↓
Email Generation
      ↓
Automated Email Sending
      ↓
User Notification
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;key-features&quot;&gt;Key Features&lt;/h3&gt;
&lt;h4 id=&quot;smart-file-upload-handling&quot;&gt;Smart File Upload Handling&lt;/h4&gt;
&lt;p&gt;The application provides a simple and responsive interface for uploading documents.&lt;/p&gt;
&lt;p&gt;Before processing, uploaded files are validated to ensure that only supported and valid files are handled.&lt;/p&gt;
&lt;h4 id=&quot;ai-generated-email-content&quot;&gt;AI-Generated Email Content&lt;/h4&gt;
&lt;p&gt;Instead of manually writing an email for every uploaded document, the system uses an AI API to analyze the extracted document content and generate a professional email message.&lt;/p&gt;
&lt;p&gt;This reduces repetitive work while maintaining consistent communication.&lt;/p&gt;
&lt;h4 id=&quot;automated-email-sending&quot;&gt;Automated Email Sending&lt;/h4&gt;
&lt;p&gt;After generating the email content, the system automatically sends the email to the intended recipient using &lt;strong&gt;PHPMailer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This removes the need for users to manually copy the generated content and send the email themselves.&lt;/p&gt;
&lt;h4 id=&quot;multi-format-document-processing&quot;&gt;Multi-Format Document Processing&lt;/h4&gt;
&lt;p&gt;The system supports text extraction from multiple document formats, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PDF documents&lt;/li&gt;
&lt;li&gt;Microsoft Word documents&lt;/li&gt;
&lt;li&gt;Presentation files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The extracted text is then passed to the AI processing stage.&lt;/p&gt;
&lt;h4 id=&quot;user-friendly-notifications&quot;&gt;User-Friendly Notifications&lt;/h4&gt;
&lt;p&gt;Toast notifications and session-based alerts provide feedback to users throughout the workflow.&lt;/p&gt;
&lt;p&gt;Users can easily identify whether an upload or email operation was successful or encountered an error.&lt;/p&gt;
&lt;h3 id=&quot;technologies-used&quot;&gt;Technologies Used&lt;/h3&gt;
&lt;h4 id=&quot;backend&quot;&gt;Backend&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;PHP&lt;/li&gt;
&lt;li&gt;PHPMailer&lt;/li&gt;
&lt;li&gt;AI API Integration&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;document-processing&quot;&gt;Document Processing&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Smalot PDF Parser&lt;/li&gt;
&lt;li&gt;PHPWord&lt;/li&gt;
&lt;li&gt;PHP Presentation Reader&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;frontend&quot;&gt;Frontend&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;system-workflow&quot;&gt;System Workflow&lt;/h3&gt;
&lt;p&gt;The overall process can be represented as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User
 │
 ▼
Upload Document
 │
 ▼
File Validation
 │
 ▼
Document Text Extraction
 │
 ▼
AI API Processing
 │
 ▼
Generate Email Content
 │
 ▼
PHPMailer
 │
 ▼
Send Email
 │
 ▼
Success / Error Notification
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main objective is to automate the communication process without requiring the user to manually read the uploaded document, write an email, and send it.&lt;/p&gt;
&lt;h3 id=&quot;why-this-project-matters&quot;&gt;Why This Project Matters&lt;/h3&gt;
&lt;p&gt;Manual email preparation can become a bottleneck when organizations regularly process documents.&lt;/p&gt;
&lt;p&gt;For example, consider an educational platform where lecturers upload course materials or reports and need to notify students or administrators whenever a document is uploaded.&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Upload → Read Document → Write Email → Send Email
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the process can be automated as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Upload → AI Processing → Email Sent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can help to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improve productivity&lt;/li&gt;
&lt;li&gt;Reduce repetitive manual tasks&lt;/li&gt;
&lt;li&gt;Improve communication consistency&lt;/li&gt;
&lt;li&gt;Integrate AI into existing applications&lt;/li&gt;
&lt;li&gt;Automate document-based workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;challenges-faced&quot;&gt;Challenges Faced&lt;/h3&gt;
&lt;p&gt;Building the system involved several technical challenges.&lt;/p&gt;
&lt;h4 id=&quot;handling-different-file-formats&quot;&gt;Handling Different File Formats&lt;/h4&gt;
&lt;p&gt;Different document formats require different approaches for extracting their content.&lt;/p&gt;
&lt;p&gt;Separate document-processing libraries were integrated to handle PDFs, Word documents, and presentation files.&lt;/p&gt;
&lt;h4 id=&quot;extracting-usable-text&quot;&gt;Extracting Usable Text&lt;/h4&gt;
&lt;p&gt;Extracted document content is not always perfectly structured.&lt;/p&gt;
&lt;p&gt;The system therefore needs to process the extracted content before sending it to the AI API.&lt;/p&gt;
&lt;h4 id=&quot;ai-integration&quot;&gt;AI Integration&lt;/h4&gt;
&lt;p&gt;Integrating AI-generated content into an automated backend workflow required handling API requests, responses, errors, and generated content appropriately.&lt;/p&gt;
&lt;h4 id=&quot;automated-email-delivery&quot;&gt;Automated Email Delivery&lt;/h4&gt;
&lt;p&gt;Email delivery introduced additional considerations such as SMTP configuration, error handling, and secure handling of email credentials.&lt;/p&gt;
&lt;h4 id=&quot;user-experience&quot;&gt;User Experience&lt;/h4&gt;
&lt;p&gt;Since multiple operations take place during the workflow, providing clear feedback to the user was important.&lt;/p&gt;
&lt;p&gt;Toast notifications and session-based messages were implemented to make the process easier to understand.&lt;/p&gt;
&lt;h3 id=&quot;future-improvements&quot;&gt;Future Improvements&lt;/h3&gt;
&lt;p&gt;There are several features planned for future versions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support for additional file formats&lt;/li&gt;
&lt;li&gt;AI-generated email subject suggestions&lt;/li&gt;
&lt;li&gt;Email scheduling&lt;/li&gt;
&lt;li&gt;Recipient management dashboard&lt;/li&gt;
&lt;li&gt;Upload history tracking&lt;/li&gt;
&lt;li&gt;Enhanced security and validation&lt;/li&gt;
&lt;li&gt;Multi-user authentication&lt;/li&gt;
&lt;li&gt;Processing and email delivery logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These improvements could make the system more suitable for larger organizational environments.&lt;/p&gt;
&lt;h3 id=&quot;source-code&quot;&gt;Source Code&lt;/h3&gt;
&lt;p&gt;The complete source code is available on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/NithilaLD/AI_Powered_Automatic_Email_Sending_System_For_File_Uploads&quot;&gt;GitHub Repository - AI-Powered Automatic Email Sending System&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Feel free to explore the project, provide feedback, or contribute improvements.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This project was a practical exploration of how &lt;strong&gt;AI can be integrated into existing web applications to automate repetitive workflows&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;By combining:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;File Uploads + Document Processing + AI + Email Automation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the system provides a practical approach to automating document-based communication.&lt;/p&gt;
&lt;p&gt;The project also provided hands-on experience with &lt;strong&gt;PHP backend development, document parsing, API integration, automated email delivery, and workflow automation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I look forward to further improving the project and exploring more ways to integrate AI into practical software solutions.&lt;/p&gt;
&lt;h3 id=&quot;also-published-on&quot;&gt;Also Published On&lt;/h3&gt;
&lt;p&gt;This article is also available on the following platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/pulse/ai-powered-automatic-email-sending-system-file-liyanarachchi-bcyxc&quot;&gt;Read on LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medium:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dulannithilaliyanarachchi.medium.com/ai-powered-automatic-email-sending-system-for-file-uploads-d22c8f563d4b&quot;&gt;Read on Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://builder.aws.com/content/3Id2VyzNFQQxwgS93ZkdJTnM5iJ/ai-powered-automatic-email-sending-system-for-file-uploads&quot;&gt;Read on AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DEV Community:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/dulannithilaliyanarachchi/ai-powered-automatic-email-sending-system-for-file-uploads-3ke5&quot;&gt;Read on DEV Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26575/ai-powered-automatic-email-sending-system-for-file-uploads</guid>
<pubDate>Wed, 09 Sep 2026 08:24:15 +0000</pubDate>
</item>
<item>
<title>Building a Multiplayer Backend with TypeScript and StreetJS</title>
<link>https://coderlegion.com/25916/building-a-multiplayer-backend-with-typescript-and-streetjs</link>
<description>&lt;p&gt;Multiplayer applications are fundamentally different from traditional REST APIs.&lt;/p&gt;
&lt;p&gt;In a typical API, a client sends a request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Client → Server → Response
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A multiplayer game needs something closer to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Player A ─┐
          ├──→ Server ──→ Player A
Player B ─┤              ├→ Player B
Player C ─┘              └→ Player C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Players are constantly sending events, receiving updates, joining rooms, leaving rooms, and reacting to what other players are doing.&lt;/p&gt;
&lt;p&gt;That makes the backend architecture extremely important.&lt;/p&gt;
&lt;p&gt;This is one of the areas I want &lt;strong&gt;StreetJS&lt;/strong&gt; to make easier.&lt;/p&gt;
&lt;h3 id=&quot;the-websocket-connection&quot;&gt;The WebSocket connection&lt;/h3&gt;
&lt;p&gt;Instead of repeatedly polling a REST endpoint, players can maintain a persistent connection with the server.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const socket = new WebSocket(
  'wss://game.example.com'
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once connected, the client can send events such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;type&quot;: &quot;player.move&quot;,
  &quot;x&quot;: 120,
  &quot;y&quot;: 340
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server processes the event and broadcasts the relevant state change to other players.&lt;/p&gt;
&lt;h3 id=&quot;rooms&quot;&gt;Rooms&lt;/h3&gt;
&lt;p&gt;A multiplayer backend usually needs the concept of a room.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Room: game-4821

Player 1
Player 2
Player 3
Player 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Players joining the same room become part of the same realtime context.&lt;/p&gt;
&lt;p&gt;The server can maintain the relationship:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Room
 ├── Player A
 ├── Player B
 ├── Player C
 └── Player D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When Player A performs an action, the server determines which players should receive the event.&lt;/p&gt;
&lt;p&gt;This is much more efficient than broadcasting every event to every connected client.&lt;/p&gt;
&lt;h3 id=&quot;the-server-should-be-authoritative&quot;&gt;The server should be authoritative&lt;/h3&gt;
&lt;p&gt;One of the biggest architectural decisions in multiplayer systems is deciding who controls the game state.&lt;/p&gt;
&lt;p&gt;The client should not be trusted to decide:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;I won.&quot;
&quot;I have 10,000 coins.&quot;
&quot;My player is at position X.&quot;
&quot;I dealt 5,000 damage.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server should validate important actions and maintain authoritative state.&lt;/p&gt;
&lt;p&gt;A simplified flow looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Player
   ↓
WebSocket Event
   ↓
Validation
   ↓
Game Logic
   ↓
Authoritative State
   ↓
Broadcast
   ↓
Other Players
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This becomes especially important when the application involves competition, rankings, virtual items, or anything that could be manipulated by a client.&lt;/p&gt;
&lt;h3 id=&quot;where-streetjs-fits&quot;&gt;Where StreetJS fits&lt;/h3&gt;
&lt;p&gt;StreetJS is designed as a TypeScript-first backend framework for Node.js.&lt;/p&gt;
&lt;p&gt;For a multiplayer application, the backend can combine several capabilities:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WebSockets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Persistent connections and realtime events.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Identify players before allowing them into protected game sessions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Validate incoming events before they reach game logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Services&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Keep game rules separate from transport logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Persist users, games, matches, rankings, inventories and other durable data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Telemetry&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Monitor connections, errors and application behavior.&lt;/p&gt;
&lt;p&gt;The important part isn't having individual features.&lt;/p&gt;
&lt;p&gt;It's having them work together as one backend architecture.&lt;/p&gt;
&lt;h3 id=&quot;realtime-state-vs-persistent-state&quot;&gt;Realtime state vs persistent state&lt;/h3&gt;
&lt;p&gt;Not everything belongs in the database.&lt;/p&gt;
&lt;p&gt;For example, temporary state such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Current player position
Current room membership
Active connection
Current match state
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;may need to live in memory or a dedicated realtime state layer for fast access.&lt;/p&gt;
&lt;p&gt;Persistent information such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User account
Match history
Leaderboard
Player statistics
Purchases
Achievements
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;belongs in durable storage.&lt;/p&gt;
&lt;p&gt;A good multiplayer architecture needs to clearly separate these two worlds.&lt;/p&gt;
&lt;h3 id=&quot;scaling-becomes-a-different-problem&quot;&gt;Scaling becomes a different problem&lt;/h3&gt;
&lt;p&gt;A single server can handle a small multiplayer application.&lt;/p&gt;
&lt;p&gt;Eventually you may have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;             Load Balancer
                  │
       ┌──────────┼──────────┐
       ↓          ↓          ↓
   Server A   Server B   Server C
       │          │          │
       └──────────┼──────────┘
                  ↓
          Shared State
                  ↓
             PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now things become more complicated.&lt;/p&gt;
&lt;p&gt;What happens when two players in the same room are connected to different servers?&lt;/p&gt;
&lt;p&gt;How do servers synchronize events?&lt;/p&gt;
&lt;p&gt;What happens when a server crashes?&lt;/p&gt;
&lt;p&gt;How do you reconnect a player?&lt;/p&gt;
&lt;p&gt;How do you prevent duplicate events?&lt;/p&gt;
&lt;p&gt;These aren't just WebSocket problems.&lt;/p&gt;
&lt;p&gt;They're distributed-systems problems.&lt;/p&gt;
&lt;h3 id=&quot;that-s-what-makes-multiplayer-interesting&quot;&gt;That's what makes multiplayer interesting&lt;/h3&gt;
&lt;p&gt;Building a multiplayer backend isn't simply:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Add WebSockets.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It's about designing a system that can handle &lt;strong&gt;state, synchronization, consistency, latency, failures and trust&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That's the part I find interesting.&lt;/p&gt;
&lt;p&gt;And it's one of the reasons I'm continuing to develop StreetJS.&lt;/p&gt;
&lt;p&gt;The goal isn't to hide these problems from developers.&lt;/p&gt;
&lt;p&gt;It's to provide solid infrastructure so developers can spend more time building the actual application.&lt;/p&gt;
&lt;h3 id=&quot;what-would-you-build-&quot;&gt;What would you build?&lt;/h3&gt;
&lt;p&gt;If you were building a multiplayer application with TypeScript today, what would you make?&lt;/p&gt;
&lt;p&gt;A multiplayer game?&lt;/p&gt;
&lt;p&gt;Collaborative editor?&lt;/p&gt;
&lt;p&gt;Real-time trading simulator?&lt;/p&gt;
&lt;p&gt;Online classroom?&lt;/p&gt;
&lt;p&gt;Competitive quiz?&lt;/p&gt;
&lt;p&gt;Something completely different?&lt;/p&gt;
&lt;p&gt;I'm interested in hearing what other developers are building and what problems you've encountered with realtime Node.js applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;StreetJS:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hassanmubiru/StreetJS&quot;&gt;https://github.com/hassanmubiru/StreetJS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://hassanmubiru.github.io/StreetJS/&quot;&gt;https://hassanmubiru.github.io/StreetJS/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;typescript-nodejs-websockets-multiplayer-backenddevelopment-opensource-javascript-softwarearchitecture&quot;&gt;TypeScript #NodeJS #WebSockets #Multiplayer #BackendDevelopment #OpenSource #JavaScript #SoftwareArchitecture&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25916/building-a-multiplayer-backend-with-typescript-and-streetjs</guid>
<pubDate>Mon, 31 Aug 2026 12:59:36 +0000</pubDate>
</item>
<item>
<title>How I Stopped a Race Condition from Creating Free Money in My Wallet System</title>
<link>https://coderlegion.com/20524/how-i-stopped-a-race-condition-from-creating-free-money-in-my-wallet-system</link>
<description>&lt;p&gt;&lt;strong&gt;The Bug That Doesn't Show Up in Testing.&lt;/strong&gt;&lt;br&gt;
When I set out to build a production-grade wallet system, I expected the hard parts to be the database schema, the transaction logic, or the authentication layer.&lt;/p&gt;
&lt;p&gt;What I didn't expect was that the most dangerous bug in the system would have nothing to do with wrong code - it would be about timing.&lt;/p&gt;
&lt;p&gt;This is the story of a race condition that lets users spend money they don't have, why it's invisible until it hits production, and how Redis turned it into a non-issue.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What a Wallet System Actually Does&lt;/strong&gt;&lt;br&gt;
Before we get to the bug, a quick picture of the system. A wallet holds a balance. Users can:&lt;/p&gt;
&lt;p&gt;Credit - add funds&lt;br&gt;
Debit - spend funds&lt;br&gt;
Transfer - move funds to another user&lt;br&gt;
Reverse - undo a transaction&lt;br&gt;
Every operation reads the current balance, validates it, and writes a new transaction record. Simple enough. The problem hides in that gap between reading and writing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Race Condition&lt;/strong&gt;&lt;br&gt;
Here's the scenario. A user has ₦5,000 in their wallet. They're on a slow connection, so they tap &quot;Pay&quot; twice. Two HTTP requests hit the server within milliseconds of each other. Both are trying to debit ₦3,000.&lt;/p&gt;
&lt;p&gt;Without any concurrency protection, this is what happens:&lt;/p&gt;
&lt;p&gt;Request A → reads balance: ₦5,000&lt;br&gt;
Request B → reads balance: ₦5,000&lt;/p&gt;
&lt;p&gt;Request A → ₦5,000 - ₦3,000 = ₦2,000 ✓ writes transaction&lt;br&gt;
Request B → ₦5,000 - ₦3,000 = ₦2,000 ✓ writes transaction&lt;br&gt;
Both requests read ₦5,000. Both pass the balance check. Both write a successful debit. The user just spent ₦6,000 from a ₦5,000 wallet - and the system called both transactions successful.&lt;/p&gt;
&lt;p&gt;This isn't a theoretical edge case. Under any real concurrent load, this happens. And the worst part: it never shows up in testing, because your test runner doesn't fire two real HTTP requests at the same millisecond.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why a Database Transaction Doesn't Save You&lt;/strong&gt;&lt;br&gt;
The instinct here is to reach for a database transaction. Wrap the whole thing in BEGIN / COMMIT and let Postgres sort it out.&lt;/p&gt;
&lt;p&gt;The problem is that the race happens before the database transaction begins. Both requests read the balance from the database (or worse, from a cache), both pass the validation check in application code, and then both open their database transactions. The damage is already done.&lt;/p&gt;
&lt;p&gt;You need a lock at the application layer, before the read even happens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Fix: Distributed Locking with Redis&lt;/strong&gt;&lt;br&gt;
The solution is to ensure that no two operations on the same wallet can run at the same time. Before any balance operation begins, it must claim exclusive access. If another operation already holds the claim, it retries until the lock is free, or gives up after a timeout.&lt;/p&gt;
&lt;p&gt;This is a distributed lock, and Redis is the right tool for it. Here's the implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// src/utils/lock.utils.ts
import { randomUUID } from &quot;crypto&quot;;
import redisClient from &quot;../services/redis.service&quot;;

const LOCK_TTL_MS = 10_000;
const LOCK_RETRY_COUNT = 50;
const LOCK_RETRY_DELAY_MS = 100;

const RELEASE_SCRIPT = `
  if redis.call(&quot;GET&quot;, KEYS[1]) == ARGV[1] then
    return redis.call(&quot;DEL&quot;, KEYS[1])
  else
    return 0
  end
`;

const acquireLock = async (key: string): Promise&amp;lt;string | null&amp;gt; =&amp;gt; {
  const token = randomUUID();
  const result = await redisClient.set(key, token, &quot;PX&quot;, LOCK_TTL_MS, &quot;NX&quot;);
  return result === &quot;OK&quot; ? token : null;
};

export const withLock = async &amp;lt;T&amp;gt;(userId: string, fn: () =&amp;gt; Promise&amp;lt;T&amp;gt;): Promise&amp;lt;T&amp;gt; =&amp;gt; {
  const key = `wallet:lock:${userId}`;
  let token: string | null = null;

  for (let attempt = 0; attempt &amp;lt;= LOCK_RETRY_COUNT; attempt++) {
    token = await acquireLock(key);
    if (token) break;
    await new Promise((resolve) =&amp;gt; setTimeout(resolve, LOCK_RETRY_DELAY_MS));
  }

  if (!token) throw new Error(&quot;Could not acquire lock — resource is busy&quot;);

  try {
    return await fn();
  } finally {
    await redisClient.eval(RELEASE_SCRIPT, 1, key, token);
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's break down each decision.&lt;/p&gt;
&lt;p&gt;SET key token PX 10000 NX&lt;br&gt;
This is a single atomic Redis command that does three things at once:&lt;/p&gt;
&lt;p&gt;NX — only set the key if it does not already exist&lt;br&gt;
PX 10000 — expire the key after 10 seconds&lt;br&gt;
The value is a unique token (UUID), not just 1 or &quot;locked&quot;&lt;br&gt;
The atomicity of SET NX is the core guarantee. There is no gap between &quot;check if key exists&quot; and &quot;set the key&quot; — it's one operation. Two requests racing to acquire the same lock cannot both succeed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Retry logic&lt;/strong&gt;&lt;br&gt;
Rather than immediately rejecting a request when the lock is held, the implementation retries up to 50 times with a 100ms delay between each attempt - giving it up to 5 seconds to acquire the lock before giving up. This handles brief overlaps gracefully without dropping legitimate requests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The TTL&lt;/strong&gt;&lt;br&gt;
The 10-second expiry is a safety net. If the application crashes mid-operation and fails to release the lock, it will expire automatically. Without this, one crash would lock that user's wallet forever.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Lua script on release&lt;/strong&gt;&lt;br&gt;
This is where most implementations get it wrong. A naive release looks like this:&lt;/p&gt;
&lt;p&gt;await redisClient.del(key); // ❌ dangerous&lt;br&gt;
Here's why that's broken: imagine a slow operation acquires the lock, but takes longer than 10 seconds. The TTL expires. A second operation acquires the lock. Now the first operation finishes and calls del - it just released the second operation's lock, not its own. The guarantee is broken.&lt;/p&gt;
&lt;p&gt;The Lua script fixes this by checking the token before deleting:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lua&quot;&gt;if redis.call(&quot;GET&quot;, KEYS[1]) == ARGV[1] then
  return redis.call(&quot;DEL&quot;, KEYS[1])
else
  return 0
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because Lua scripts execute atomically in Redis, the check-and-delete is a single uninterruptible operation. You can only release a lock you actually own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The withLock wrapper&lt;/strong&gt;&lt;br&gt;
Rather than exposing raw acquireLock / releaseLock functions and trusting every caller to use them correctly, the implementation wraps the entire pattern in a higher-order function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// src/services/wallet.service.ts
export const debitWallet = async (userId: string, amount: number): Promise&amp;lt;Transaction&amp;gt; =&amp;gt; {
  return await withLock(userId, async () =&amp;gt; {
    const wallet = await prisma.wallet.findUnique({ where: { userId } });

    if (!wallet || wallet.balance.lessThan(amount)) {
      throw new Error(&quot;Insufficient balance&quot;);
    }

    // create transaction, update balance...
    return transaction;
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The finally block inside withLock ensures the lock is always released, even if the operation throws. A lock that leaks on error is as bad as no lock at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preventing Deadlocks on Transfers&lt;/strong&gt;&lt;br&gt;
A transfer touches two wallets - sender and receiver. If two transfers run simultaneously in opposite directions (A→B and B→A), they can deadlock: each holds one lock and waits for the other indefinitely.&lt;/p&gt;
&lt;p&gt;The fix is to always acquire locks in the same order, regardless of who is sending to whom. We sort the user IDs before acquiring:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export const withMultiLock = async &amp;lt;T&amp;gt;(userIds: string[], fn: () =&amp;gt; Promise&amp;lt;T&amp;gt;): Promise&amp;lt;T&amp;gt; =&amp;gt; {
  const sortedIds = [...new Set(userIds)].sort();
  const keys = sortedIds.map((id) =&amp;gt; `wallet:lock:${id}`);
  const tokens: string[] = [];

  try {
    for (const key of keys) {
      let token: string | null = null;
      for (let attempt = 0; attempt &amp;lt;= LOCK_RETRY_COUNT; attempt++) {
        token = await acquireLock(key);
        if (token) break;
        await new Promise((resolve) =&amp;gt; setTimeout(resolve, LOCK_RETRY_DELAY_MS));
      }
      if (!token) throw new Error(&quot;Could not acquire lock — resource is busy&quot;);
      tokens.push(token);
    }
    return await fn();
  } finally {
    await Promise.all(keys.map((key, i) =&amp;gt; releaseLock(key, tokens[i])));
  }
};

// In transfer service:
const result = await withMultiLock([senderId, receiver.id], async () =&amp;gt; {
  // both wallets locked — safe to proceed
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If A→B and B→A both arrive at the same time, both will sort to [A, B] and try to acquire A's lock first. One wins, the other waits. No deadlock.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Full System&lt;/strong&gt;&lt;br&gt;
The distributed lock is one layer in a larger stack. The complete wallet engine includes:&lt;/p&gt;
&lt;p&gt;Idempotency - Idempotency-Key header on credit and debit prevents duplicate transactions on network retries, backed by a 24-hour Redis TTL&lt;br&gt;
Webhook delivery with retry - BullMQ queue, 4 attempts, exponential backoff (2s base), per-attempt audit trail&lt;br&gt;
Balance caching - 60-second Redis TTL, invalidated on every write&lt;br&gt;
Token blacklisting - SHA-256 hashed JWTs stored in Redis with matching expiry, making logout actually secure&lt;br&gt;
Cursor-based pagination - transaction history that scales&lt;br&gt;
Full test suite - 103 tests, all mocked, no database required to run.&lt;br&gt;
Built with TypeScript, Express, Prisma, PostgreSQL, and Redis (ioredis). Deployed on Render with a GitHub Actions CI/CD pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why This Matters&lt;/strong&gt;&lt;br&gt;
Race conditions in financial systems are not exotic. They're the default outcome when you don't explicitly prevent them. Most wallet tutorials never mention it. Most wallet implementations in production have it.&lt;/p&gt;
&lt;p&gt;The fix isn't complicated - but you have to know to look for it.&lt;/p&gt;
&lt;p&gt;If you're building anything that touches money, check your balance reads. If there's no lock between the read and the write, the race condition is already there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open for Collaboration&lt;/strong&gt;&lt;br&gt;
This project is open source and actively welcoming contributors. Whether you want to add a feature, improve test coverage, or spot something that could be done better - PRs are open.&lt;/p&gt;
&lt;p&gt;Check out the CONTRIBUTING.md for how to get set up and where to send your changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tochi27/Wallet_System.git&quot;&gt;https://github.com/tochi27/Wallet_System.git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live API + Swagger:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://wallet-system-api-16cv.onrender.com/api-docs&quot;&gt;https://wallet-system-api-16cv.onrender.com/api-docs&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20524/how-i-stopped-a-race-condition-from-creating-free-money-in-my-wallet-system</guid>
<pubDate>Sat, 13 Jun 2026 14:56:16 +0000</pubDate>
</item>
<item>
<title>From CRUD APIs to Production-Style Backend Architecture: My Video Sharing Platform Project</title>
<link>https://coderlegion.com/19204/from-crud-apis-to-production-style-backend-architecture-video-sharing-platform-project</link>
<description>&lt;p&gt; Completed a Production-Style Video Sharing Platform Backend API&lt;br&gt;
Over the past few weeks, I've been building a backend system inspired by modern video-sharing platforms using Node.js, Express.js, MongoDB, and Cloudinary.&lt;/p&gt;
&lt;p&gt;This project went far beyond CRUD operations and helped me understand how real backend systems are designed.&lt;/p&gt;
&lt;p&gt;Features Implemented:&lt;br&gt;
✅ JWT Authentication &amp;amp; Refresh Token Rotation&lt;br&gt;
✅ Secure HTTP-Only Cookie Based Sessions&lt;br&gt;
✅ Video Upload &amp;amp; Cloudinary Integration&lt;br&gt;
✅ Comment System&lt;br&gt;
✅ Like System (Videos, Comments, Tweets)&lt;br&gt;
✅ Subscription System&lt;br&gt;
✅ Playlist Management&lt;br&gt;
✅ Watch History Tracking&lt;br&gt;
✅ Creator Dashboard Analytics&lt;br&gt;
✅ Pagination &amp;amp; Aggregation Pipelines&lt;/p&gt;
&lt;p&gt;Key Things I Learned:&lt;br&gt;
 Authentication using Access &amp;amp; Refresh Tokens&lt;br&gt;
 Designing relationships in MongoDB&lt;br&gt;
 MongoDB Aggregation Pipelines ($lookup, $group, $project, $addFields)&lt;br&gt;
 Ownership-Based Authorization&lt;br&gt;
 Cloudinary Media Management&lt;br&gt;
 Cascade Cleanup Logic&lt;br&gt;
 Building consistent API response structures&lt;/p&gt;
&lt;p&gt;Interesting Debugging Challenges:&lt;br&gt;
Duplicate MongoDB index issues&lt;br&gt;
Populate path errors&lt;br&gt;
Refresh token validation flow&lt;br&gt;
Route mismatches&lt;br&gt;
Aggregation pipeline bugs&lt;br&gt;
Watch history synchronization&lt;br&gt;
Video deletion cleanup across collections&lt;/p&gt;
&lt;p&gt;Documentation Created:&lt;br&gt;
 Complete API Documentation&lt;br&gt;
 Authentication Flow Documentation&lt;br&gt;
️ Database Schema Documentation&lt;/p&gt;
&lt;p&gt;What's Next?&lt;br&gt;
Building the React frontend and converting this into a complete full-stack application.&lt;/p&gt;
&lt;p&gt;GitHub Repository: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/SairajJadhav08/Video-Sharing-Platform-API&quot;&gt;https://github.com/SairajJadhav08/Video-Sharing-Platform-API&lt;/a&gt;&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/19204/from-crud-apis-to-production-style-backend-architecture-video-sharing-platform-project</guid>
<pubDate>Fri, 29 May 2026 09:30:58 +0000</pubDate>
</item>
<item>
<title>Rebuilding RouteMatrix: From Generic Tool to Logistics SaaS</title>
<link>https://coderlegion.com/12088/rebuilding-routematrix-from-generic-tool-to-logistics-saas</link>
<description>&lt;p&gt;A few months ago, I shipped the first version of &lt;strong&gt;RouteMatrix&lt;/strong&gt;.&lt;br&gt;
It worked.&lt;/p&gt;
&lt;p&gt;But something felt off.&lt;/p&gt;
&lt;p&gt;Instead of blindly iterating on features, I paused and asked a harder question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What problem am I actually solving?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That question changed everything.&lt;/p&gt;
&lt;p&gt;I realized RouteMatrix shouldn’t be a generic trip tool.&lt;br&gt;
It needed to solve real operational pain in logistics.&lt;/p&gt;
&lt;p&gt;So I’m rebuilding it from the ground up — as a &lt;strong&gt;Tracking Truck SaaS&lt;/strong&gt; focused on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fleet visibility  &lt;/li&gt;
&lt;li&gt;Trip accountability  &lt;/li&gt;
&lt;li&gt;Operational clarity for logistics teams  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn’t a UI refresh.&lt;br&gt;
It’s a full rethink:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New architecture  &lt;/li&gt;
&lt;li&gt;New data model  &lt;/li&gt;
&lt;li&gt;New mental model  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m documenting the rebuild openly — decisions, mistakes, and trade-offs — in public.&lt;/p&gt;
&lt;p&gt;If you’re building in logistics, fleet management, or SaaS infrastructure, let’s exchange ideas&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=12108966631621092542&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12088/rebuilding-routematrix-from-generic-tool-to-logistics-saas</guid>
<pubDate>Mon, 23 Feb 2026 19:37:36 +0000</pubDate>
</item>
</channel>
</rss>