Skip to content

Collection of tips, tricks, and code snippets related to backend development. It covers various topics such as API handling, databases, and more.

Notifications You must be signed in to change notification settings

edriso/server-side-tips-n-tricks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 

Repository files navigation

Server Side Tips N Tricks

1- A social app, handle API for add/remove post from favorites.

What I did:

  • Implemented two distinct endpoints: one for adding posts to favorites and another for removing them.
  • Additionally, when removing a post from favorites, if the post wasn't already in favorites, I returned an error message.

Code Review:

  • Instead of returning error messages when the post isn't found, it's better to simply return without any message, especially to account for scenarios where users may click multiple times.
  • For a more simpler and minimal API, consider combining these operations into a single endpoint for toggling between adding and removing posts from favorites. _ Credits: Saif

2- In our app, we aim to show various types of posts like all user posts, a single post, most viewed posts, and posts similar to a specific one.

What I did: Made individual endpoints for each type of post.

Code Review: It's more efficient to create just a couple of endpoints—one for fetching all posts and another for fetching a single post. Then, we can use filters to adjust what we get back based on what the user needs. This not only simplifies the API but also gives us more freedom to adapt to future changes easily.

_ Credits: Saif


3- Imagine an app where clients can subscribe to different bundles. We want to keep track of how much of each bundle a client has left and how many times they've used it.

What I did: Added 'remaining' and 'consumed/used' columns to the pivot table connecting bundles and clients, and if we need the total amount, we add them up.

Code review: It might be clearer to use a 'total' column instead of 'used'. Here's why: With the first approach, when a client uses a bundle, we increase the 'remaining' and decrease the 'used'. However, with the other approach, when a client uses a bundle, we only adjust the 'remaining', and when they subscribe to a bundle, we increase the 'total'. This second approach offers less functionality. Then, if we need to find out how many times a bundle has been used, we can calculate it by subtracting the 'remaining' from the 'total'.

_ Credits: Saif


4- Continuing from the previous Subscribe to a bundle (without payment).

What I did: I added a method named subscribeToBundle in the Bundle model. It accepts a bundle's ID as a parameter and handles different cases: if the bundle doesn't exist, it returns an error message response; if the client already has the bundle, it returns a response indicating that it was extended; and if there's no existing relation, it returns a response indicating successful subscription.

Code Review: It's not recommended to handle responses directly from the model; that's the controller's job. Additionally, it's crucial to keep in mind that this method might be used in various contexts like web or API, where the response format could differ. Hence, it's better to handle responses in the controller to maintain flexibility across different implementations.

_ Credits: Saif


5- While working on an old project, I encountered errors after cloning the app and running migrations.

Error 1.0: A late migration had a foreign key referencing a prior migration, causing an error. Solution 1.0: I renamed the file to ensure it comes before the referenced migration. Error 1.1: It led to a production issue because a new migration file was created, attempting to create a table that already existed in the database, causing schema conflicts. Solution 1.1: I added a check to ensure the table doesn't exist before running the logic.

Error: Another error occurred in a different migration, and I wasn't sure why. Solution: It was due to an old package updating its migration. I deleted the file, reran the package terminal to generate the new migration file, and added a check at the top to prevent problems with existing tables on the server.

public function up(): void
{
    if (!Schema::hasTable('tableName')) {
      //...
    }
}

_ Credits: Saif


6- Working on a live app, we needed to update the 'name' column to be of JSON type instead of VARCHAR/String.

What I did: Initially attempted to change the type directly, but encountered an error due to existing string values in the column. So, I found a solution to create a new column called new_name, looped through each name and added it to new_name column in JSON format, then deleted named column and renamed new_name to be name, I also learned how to add new_name column directly after name column.

Schema::table('users', function (Blueprint $table) {
    Schema::table('users', function (Blueprint $table) {
        $table->json('new_name')->after('name')->nullable();
    });

    // During code review, Saif opted for raw SQL queries over ORM
    // for more efficient execution.
    DB::table('users')->update(
      ['new_name' => DB::raw('JSON_OBJECT("en", name, "ar", name)')]
    );

    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('name');
        $table->renameColumn('new_name', 'name');
    });
});

7- Sometimes your code needs space for better readability.

Before:

Schema::create('users', function (Blueprint $table) {
  $table->id();
  $table->string('name');
  $table->string('email')->unique();
  $table->timestamp('email_verified_at')->nullable();
  $table->string('password');
  $table->string('phone_e164')->nullable()->unique();
  $table->timestamp('phone_verified_at')->nullable();
  $table->foreignId('country_id')->nullable()->constrained()->nullOnDelete();
  $table->foreignId('city_id')->nullable()->constrained()->nullOnDelete();
  $table->foreignId('area_id')->nullable()->constrained()->nullOnDelete();
  $table->rememberToken();
  $table->timestamps();
});

protected $fillable = [
  'name',
  'email',
  'email_verified_at',
  'password',
  'phone_e164',
  'phone_verified_at',
  'country_id',
  'city_id',
  'area_id',
];

After:

Schema::create('users', function (Blueprint $table) {
  $table->id();
  $table->string('name');
  $table->string('email')->unique();
  $table->timestamp('email_verified_at')->nullable();

  $table->string('password');

  $table->string('phone_e164')->nullable()->unique();
  $table->timestamp('phone_verified_at')->nullable();

  $table->foreignId('country_id')->nullable()->constrained()->nullOnDelete();
  $table->foreignId('city_id')->nullable()->constrained()->nullOnDelete();
  $table->foreignId('area_id')->nullable()->constrained()->nullOnDelete();

  $table->rememberToken();
  $table->timestamps();
});

protected $fillable = [
  'name',
  'email',
  'email_verified_at',

  'password',

  'phone_e164',
  'phone_verified_at',

  'country_id',
  'city_id',
  'area_id',
];

_ Credits: Saif


8- Working on an app where users can make posts, and posts expire after a day of admin approval, and feeds show active posts.

What I did:

  • Added a status column to the posts table, handled as an enum where the default is 'pending'.
  • Added an expires_at column that updates when the admin approves a post.
  • Added another enum value to status called 'expired'.
  • Implemented a cron job that runs every minute to check for 'active' posts that have passed the current time. If found, I changed their status to 'expired'.
  • On the feeds page, I display posts where the status is `active'.

Code Review:

  • It's a good approach, but instead of running a cron job, consider returning posts with 'active' status AND expires_at is greater than the current time.
  • Handling Manual Expiry: And for users manually expire a post, we could set expires_at to the current time or to null.

_ Credits: Saif


9- Logout Options: Logout from Current Device or All Devices

When implementing the logout functionality, you can provide the client with the option to log out either from the current device or from all devices. This feature enhances security and gives users control over their sessions.

$request->has('from_all')
            ? $this->user->tokens()->delete()
            : $this->user->currentAccessToken()->delete();
public function logout(Request $request)
{
    // Check if the request has the 'from_all' parameter
    if ($request->has('from_all')) {
        // Delete all tokens associated with the user, logging them out from all devices
        $this->user->tokens()->delete();
    } else {
        // Delete only the current access token, logging the user out from the current device
        $this->user->currentAccessToken()->delete();
    }

    return response()->json(['message' => 'Successfully logged out']);
}

Benefits

  1. Enhanced Security: Allowing users to log out from all devices can help secure their accounts in case they suspect any unauthorized access.
  2. User Control: Users can manage their sessions more effectively, choosing to maintain access on trusted devices while logging out from others.

Note

Ensure proper validation and error handling are in place to handle edge cases, such as when there are no active tokens.

public function logout(Request $request)
{
    if ($request->has('from_all')) {
        $this->user->tokens()->delete();
    } else {
        $token = $this->user->currentAccessToken();
        if ($token) {
            $token->delete();
        } else {
            return response()->json(['message' => 'No active session found'], 400);
        }
    }

    return response()->json(['message' => 'Successfully logged out']);
}

This enhanced note provides a clear explanation of the functionality, usage examples, and highlights the benefits, making it easy for users and developers to understand and implement.

Credits: Saif and chatGPT


10- Component or no component? (Note on Reusable Components vs. Direct Styling)

During a recent task to refactor the 'create new address' form, I attempted to create a reusable form-field component to streamline the handling of form inputs across the Magento_Customer module. Although the idea was to reduce code repetition and enhance reusability, I encountered significant challenges that highlighted a few important considerations:

  1. Balancing Reusability and Complexity:
  • The goal of creating a reusable component is to reduce code duplication with minimal dependencies. However, in my case, the extensive number of properties required, along with integrating Alpine.js directives and PHP variables, made the component overly complex and difficult to manage.
  1. Ease of Maintenance and Debugging:
  • The complexity introduced by the reusable component, especially with the interplay of JavaScript and PHP, made the code harder to maintain and debug. Directly styling the individual fields would have resulted in a simpler structure, making future updates and fixes easier to implement.
  1. Readability and Collaboration:
  • The combination of Alpine.js expressions and PHP variables in the component led to confusion, making it challenging for other developers to understand the logic and purpose of the code. For such cases, where there are variations in field requirements, keeping the logic straightforward and closer to the form fields is preferable. In conclusion, while reusable components are beneficial in scenarios with high consistency and minimal variation, in this particular case, applying styles directly to the fields would have been a better choice. It would simplify the codebase and make it more maintainable and accessible to others working on it.

_ Credits: Mahmoud Shiref


11- Time Estimation Tips

Some tips for time estimation that were shared with us long ago. They might be helpful to re-share:

Recommendation:

  • Do estimation not for hours, but for an action plan (if you are a developer):

    • As a developer, focus on thinking through step-by-step actions needed to be performed to deliver the request.
    • Include your work, think about how it will affect the codebase, where to make amendments, and highlight if Growth/QA/Design will need to have their input. Use it as your checklist for the upcoming work.
    • Only when you have listed the action plan, start adding per-position timeframes. With such an approach, you will be much more accurate with your estimate.
    • If you think of hours first, you will limit your brain from considering edge cases and the true real-life scope. You might think, "40 hours should be enough," but when considering all the work like X/Y/Z/A/H/F, it could actually be more than 70 hours. So, prepare action steps first, and only then add the time needed per step.
  • Never skip estimation, even for small and quick requests:

    • At first, it seems like a time-saver, but as the project goes on, it will uncover hidden challenges that add to your burden and your PM’s.
  • Don't guess estimates. It is the worst thing to do:

    • Prepare the plan first, and only after that add hours per position.
  • Don't create open scope positions in the estimate:

    • For example, don't add things like "Page X styling per design". Instead, list down "Product description", "Product reviews", "Product gallery", etc.
    • This helps define more specific actions and avoids misunderstandings. For instance, if you had a bad estimate for a PDP, the client might think the header/footer is included, while your estimate only covers the main body content.
  • Review estimates to see whether you can break their logic:

    • Re-read your estimates as if you were the PO/Client/PM and try to include things that you as a developer did not intend to do. It helps you avoid giving a false impression.
  • Don't guess estimates:

    • Prepare the plan first, and only after that add hours per position.

Additional Tips:

  • When you start a new task, provide a similar estimation and to-dos. For example, set your original estimate in a Jira ticket as an average between ranges. So if your task is:

    • Doing A: 2-4 hours
    • Doing B: 4-8 hours

    Your original estimate would be 9 hours as it is the average between the range of 6-12 hours of your estimate.

    Remember, your range is always from x to 2x, which gives you a lot of flexibility. It's okay to exceed the "original estimate" as it's just an average, but try to stay within your range.

    Why We Do This:

    1. It helps align on task understanding.
    2. It helps validate your plan to ensure no tech steps are missed or redundant.
    3. It makes checking the power of the estimate more objective as the estimate comes from you.
    4. It allows time for investigating the ticket, so you can make the plan smart. The time for investigation can be from a few hours to a day, depending on the ticket or experience.

_ Credits: Mahmoud Shiref


About

Collection of tips, tricks, and code snippets related to backend development. It covers various topics such as API handling, databases, and more.

Topics

Resources

Stars

Watchers

Forks