Which backend framework to choose in 2021?

I've done professional web development for 12 years now and a couple of years as a hobby before. All web applications seem to be almost the same these days; spa frontend, crud (create,read,update,delete) endpoints and a couple of integrations. Sometimes the api is used for mobile apps as well.

As a lazy person I have a little obsession with productivity. I always seek for ways to achieve the same result with less effort (or more results with the same effort). I decided to go through a lot of backend technologies to see what's out there and does it matter which one is used. Could there be a framework that does the same as any other, but with less effort?

When is programming easy and productive?

Of course many factors exist in addition to those like learning, maintainability and matter of taste. Anyway here's a comparison when considering only popularity, features and vitality of various backend frameworks.

Choosing programming languages

I used the following sources to compare the popularity of technologies

By combining those statistics I got the most popular programming languages

Language Tiobe PyPL Stackshare Stack overflow SH Framework
Python 3 1 2 4 1 (Django)
JavaScript 7 3 1 1 4 (Express)
Java 2 2 4 5 6 (Spring)
C# 5 4 6 7 2 (ASP.NET)
PHP 8 6 3 9 3 (Laravel)
C 1 5 17 11
C++ 4 5 15 10
R 9 7 17
Objective-C 18 8 14 20
Swift 16 9 13 16
TypeScript 46 10 5 8 4 (Express)
Matlab 15 11
Kotlin 33 12 18 13 6 (Spring)
Go 14 13 12 12
Ruby 13 15 7 14 5 (Rails)
VBA 14 15
Rust 25 16 8 19
Scala 21 17 16 21
Visual Basic 6 18
Dart 26 21 10 22

Only a few programming languages have a popular backend framework so top 5 backend languages are easily found out to be Python, JavaScript/TypeScript, Java, C# and PHP.

Choosing frameworks

Framework doesn't need to be the most popular, just popular enough. Next step was to search other backend frameworks for chosen languages. I used Stackshare and Google searches. I looked for Stackshare user count, latest release date, number of contributors from Github and questions from Stack overflow. Then I chose frameworks matching these criterias

For closer inspection 29 frameworks were chosen (and 58 ruled out)

Python:
AIOHTTP, Django, Django REST Framework, Falcon, FastAPI, Flask, Tornado
Apistar, Bottle, Cornice, Eve, Flask Api, Flask restful, Guillotina,
Hug, Sandman2, Sanic, Tastypie, Turbogears, Vibora, Web2py

Java:
Akka HTTP, Dropwizard, Micronaut, Play framework, Quarkus, Spring, Vert.x
CRNK, Elide, Javalin, Jersey, Jooby, Micro-server, Rapidoid, Ratpack,
Rest.li, RestEasy, RestExpress, Restlet, RestX, Spark Java, Spincast

JavaScript/TypeScript:
Express, Hapi, Feathers, Koa, NestJS, Sails
Actionhero.js, AdonisJS, DerbyJS, Fastify, Huncwot, KeystoneJS, LoopBack, Meteor, Moleculer, QEWD.js, Restify, ThinkJS, Tinyhttp, Total.js

C#:
ASP.NET Core, ServiceStack
ASP.NET, Carter, LightNode, Nancy, Restier

PHP:
CakePHP, CodeIgniter, Laravel, Phalcon, Slim, Symfony
API platform, Laminas, Lumen, Nette, PSX, Silex, Spiral, Waterpipe, Yii

Comparing framework features

I read the official documentation for each framework and formed a set of 50 common features. I left out some features that aren't that essential when using spa client (e.g. cookies, sessions, template engines). Estimation for each feature and framework was made by reading the docs and making Google searches (e.g. "Spring fallback route").

Then I gave effort estimations using these rules
1 = copy-paste solution from official documentation
2 = copy-paste solution from Google search / some library does exactly what needed
3 = no clear solution, but seems possible by combining search results / libraries
4 = no relevant search results (first ~10)

Features

RESTful routing (method + path + parameters)

// Express
app.get('/products/:id(\\d+)', function (req, res) {
  // ...
})

Reverse routing (generating url from route name + parameters)

# Django
urlpatterns = [
    path('products/<int:id>/', views.product_details, name='product-details'),
]

id = 123
url = reverse('product-details', args=(id))

Semantic versioning

// Restify
server.get('/products/:id', restify.plugins.conditionalHandler([
  { version: '1.1.0', handler: getProductByIdV1 },
  { version: '2.0.0', handler: getProductByIdV2 }
]));

Subdomain routing

// Laravel
Route::domain('{client}.example.com')->group(function () {
    Route::get('products', function ($client) {
        // ...
    });
});

Static files (serve files from /x in filesystem as /y)

// ASP.NET Core
app.UseStaticFiles(new StaticFileOptions
{
   FileProvider = new PhysicalFileProvider("path/to/files"),
   RequestPath = "/assets"
});

Rate limiting (e.g. x requests in a minute)

// Laravel
RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000);
});
Route::middleware(['throttle:global'])->group(function () {
    Route::get('/products', function () {
        //
    });
});

Middleware: Decorate/terminate/filter request, modify response

# AIOHTTP
@middleware
async def example_middleware(request, handler):
  # before / filter / decorate / terminate
  resp = await handler(request)
  # after
  return resp

Middleware conventions

// ASP.NET Core
[ExampleFilter] // apply to all sub classes
public abstract class ExampleControllerBase : ControllerBase {}

[ExampleFilter] // apply to all actions
public class ExampleController : ControllerBase 
{ 
  [ExampleFilter] // apply to single action
  [HttpGet("")]
  public IActionResult SomeAction() {}
}

Content-negotiation (convert to/from xml/json/... depending on headers)

// ASP.NET Core
// Add XML support
services.AddControllers()
  .AddXmlSerializerFormatters();

Dependency injection

// NestJS
export class ProductsController {
  // service is injected
  constructor(private productService: ProductService) {}
}

Model binding

// NestJS
@Put(':id')
updateProduct(@Param('id') id: string, @Body() product: ProductModel) {
  // ...
}

Validation

// NestJS
export class ProductModel {
  @IsNotEmpty()
  name: string;
  // ...
}

Caching

// ASP.NET Core
public class NewsController : Controller
{
  [HttpGet]
  [ResponseCache(VaryByQueryKeys = new[] { "category" },  Duration = 300)]
  public NewsModel GetMostReadNews(string category)
  {
    // ...
  }
}

Auth:
Username+password, Google/Facebook/..., roles, jwt, policies, conventions

OpenAPI

Messaging:
Broadcasting (websocket), events

Task scheduling

// NestJS
@Injectable()
export class TasksService {

  @Cron('0 */5 * * * *')
  handleCron() {
    // ...
  }
}

GraphQL:
queries, resolvers, mutations, subscriptions

Results

Almost all frameworks had some features for which obvious solutions weren't found or were quite tedious (dark bars in the picture).
Of course there's ~1500 combinations so I only spent a couple of minutes per framework+feature combo.


Most common shortcomings

Top frameworks

10. Symfony 98p (1.96p/feature)
Shortcomings listed above. GraphQL especially tedious.

9. Quarkus 96p (1.92p/feature)
Lack of semantic versioning. Tedious to use social media authentication.

9. Django 96p (1.92p/feature)
Lack of model binding leads to repetitive work. View ideology isn't really suitable for RESTful routing.

7. Django REST framework 95p (1.9p/feature)
Same as normal Django

6. ServiceStack 84p (1.68p/feature)
Lack of semantic versioning. Couldn't find anything about GraphQL (reason for not making it to the top5).

5. Laravel

Really good documentation with good examples for many problems. Biggest shortcoming is the lack of typed request and respose models which leads to repetitive validation logic. Then the same schema also needs to defined for OpenAPI

80p (1.6p/feature)

Good

Bad

4. Spring Boot

Deserves better ranking than what would be just by comparing points. Spring has a lot of features. Unfortunately most of the solutions weren't found from the official documentation but from baeldung.com. Could be a problem if that site isn't updated.

91p (1.82p/feature)

Good

Bad

3. FastAPI

Nice minimal approach. Most of the features are easy to implement. Some (rare?) features didn't have a clear solution.

87p (1.74p/feature)

Good

Bad

1. NestJS / ASP.NET Core

NestJS had a good documentation and a lot of features. ASP.NET Core was the only framework having an easy solution to all of the features.

Which is better depends a lot on how well the language (TypeScript/C#) and the platform (Node/.Net) can be utilized.

NestJS

72p (1.44p/feature)

Good

Bad

Might be #1 when

ASP.NET Core

60p (1.2p/feature)

Good

Bad

Might be #1 when