DEV Community

linou518
linou518

Posted on

When Your Frontend Hits an API That Doesn't Exist — Debugging 405 Method Not Allowed

Got a bug report: "Renaming a project returns 405 Method Not Allowed" on a self-built dashboard. The cause was simple, but it's a classic gotcha when maintaining SPAs long-term, so I'm documenting it.

Symptoms

On the project list page, inline-editing a project name and saving triggers POST /api/simple-tasks/rename 405 (METHOD NOT ALLOWED) in the browser console. Flask's server log showed the same 405.

Root Cause: Frontend-Backend Route Mismatch

Flask had /api/update-project-name registered in a different context, but the frontend JavaScript was calling /api/simple-tasks/rename.

In other words:

  • The frontend (SPA) was written to use a new API path
  • The backend (Flask) never got the corresponding route added

Flask returns 405 because it doesn't know the path. You might expect 404, but when another route with the same prefix exists, Flask's routing can interpret it as "path matches but method doesn't" — hence 405 instead of 404.

The Fix

Added the corresponding endpoint near the existing simple-tasks routes in server.py:

@app.route('/api/simple-tasks/rename', methods=['POST'])
def simple_tasks_rename():
    data = request.get_json()
    project_id = data.get('project')
    new_name = data.get('name')

    if not project_id or not new_name:
        return jsonify({'error': 'project and name required'}), 400

    tasks_data = load_simple_tasks()
    for proj in tasks_data.get('projects', []):
        if proj['id'] == project_id:
            proj['name'] = new_name
            break
    else:
        return jsonify({'error': 'project not found'}), 404

    save_simple_tasks(tasks_data)
    return jsonify({'ok': True})
Enter fullscreen mode Exit fullscreen mode

The Trap: Zombie Processes

After writing the fix and restarting Flask, the 405 persisted.

The culprit: the old Flask process was still holding the port. systemctl restart failed to properly kill the old process, and the new one couldn't start.

# Find and kill the culprit
fuser -k <PORT>/tcp

# Start fresh
systemctl --user start task-dashboard.service
Enter fullscreen mode Exit fullscreen mode

Only then did the fix take effect.

Lesson: API Route Management in SPA + Custom Backend

When maintaining a 1000+ line SPA and a 2000+ line Flask backend solo (+ AI), forgetting to add the backend route when adding a frontend feature just happens.

Prevention

  1. Maintain an API route inventory. Keep an endpoint list in your README or project docs. Update it every time you add a route.
  2. Grep your frontend fetch calls. Periodically run grep -r "fetch\|axios\|/api/" index.html and cross-reference against backend routes.
  3. Print routes on startup. Flask's app.url_map can dump all routes to the startup log.
  4. Watch for zombie processes. When a fix doesn't take effect, check what's holding the port (fuser / lsof -i).
# Print all routes on Flask startup
with app.test_request_context():
    for rule in app.url_map.iter_rules():
        print(f"  {rule.methods} {rule.rule}")
Enter fullscreen mode Exit fullscreen mode

Conclusion

405 Method Not Allowed means either "route doesn't exist" or "wrong method." When growing a custom API alongside an SPA, route mismatches between frontend and backend are inevitable. Maintain an API inventory and update both sides simultaneously.

And when your fix doesn't seem to take effect — check fuser first. Nine times out of ten, an old process is squatting on the port.

Top comments (0)