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.
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.
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
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)
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
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
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).
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
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
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
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.
72p (1.44p/feature)
Good
Bad
Might be #1 when
60p (1.2p/feature)
Good
Bad
Might be #1 when