12. Conclusion
This document has presented the development of a MVC web application using the [Flask] framework: an application whose HTML pages are generated by the server, using [Jinja2] views, without a browser-side [JavaScript] framework. He followed the [Spring MVC] course step by step, which itself builds on the [ASP.NET Core MVC] and [NestJS] courses: actions and routing to views, forms, internationalization, security, and data access, followed by a comprehensive case study.
The same 28 examples and the same application were thus written using four frameworks: [TypeScript], [C#], [Java], and [Python]. The concepts are the same; only the names and mechanisms differ. The correspondences between [Spring MVC] and [Flask]:
Spring MVC | Flask |
class [@SpringBootApplication], classes [@Configuration], methods [@Bean] | factory [create_app], objects stored in [app.extensions] |
file [pom.xml], starters [Maven] | file [requirements.txt], virtual environment, [pip] |
[application.properties], profiles, [@ConfigurationProperties] | [config.py], [config_<profile>.py], variables [FLASK_...] ([app.config]) |
Dependency injection: singleton beans, [@Service], [@Component] | objects created manually by the factory, retrieved by [current_app] |
[@Controller], [@GetMapping], [@PostMapping], view name, or [redirect:...] | blueprints, [@bp.get], [@bp.post], [render_template], [redirect] |
model link, [@RequestParam], converters | [request.args], [request.form], [Werkzeug] converters ([<int:id>]) |
[Bean Validation]: [@NotBlank], [@Min], [@Max]..., [@Valid], [BindingResult] | [Pydantic] (parameters, JSON); [WTForms]: validators, [form.validate()], [form.errors] |
views [Thymeleaf] ([.html]), [th:text], [th:each], [th:if] | views [Jinja2], [{{ }}], [{% for %}], [{% if %}] |
template and fragments ([th:replace], [th:fragment]) | [{% extends %}] and blocks, [{% include %}], macros |
[@ControllerAdvice] and [@ModelAttribute] | context processor ([@app.context_processor]) |
flash attributes ([RedirectAttributes]) | flash messages ([flash], [get_flashed_messages]), in the session cookie |
servlet filters, interceptors | WSGI, [before_request], [after_request], [teardown_request] middleware |
[@ControllerAdvice] / [@ExceptionHandler], [ErrorController] | [@app.errorhandler] |
[Spring Security]: [SecurityFilterChain], resource server, [@PreAuthorize] | [before_request] and hand-written decorators ([PyJWT], [bcrypt]) |
CSRF protection of [Spring Security] | [CSRFProtect] from [Flask-WTF] |
JSON dictionaries read by a [MessageSource], [#{...}], or [LocaleResolver] | JSON dictionaries, [t(...)] view function, [g.language] set by a [before_request] |
[Spring Data JPA] and [Hibernate], [JpaRepository], derived queries, [JPQL] | [SQLAlchemy]: mapped entities, [select(...)], sessions, handwritten DAO classes |
[@Transactional], [TransactionTemplate] | [with sessions.begin() as session:] |
[@Version], managed by [Hibernate] | [version_id_col], managed by [SQLAlchemy] |
[mvnw spring-boot:run], [Spring Boot DevTools] | [python app.py] in debug mode (automatic reload) |
executable archive ([java -jar]), with [Tomcat] included | WSGI server: [Waitress], [Gunicorn] |
Compared to [Spring MVC], the key points to note are:
- [Flask] is a micro-framework: it provides routing, requests and responses, the [Jinja2] views, and session management—and that’s it. Everything else (validation, forms, security, data access, translation) is handled by libraries chosen by the developer or written by the developer. This means more code to write, but the code is transparent: nothing happens “by magic,” and you can track every object from its creation (in [create_app]) to its use;
- there is no dependency injection container: shared objects are created by the factory, stored in [app.extensions], and retrieved by [current_app]. For an application of this size, it’s simple and sufficient;
- The objects [request], [g], [session], and [current_app] are proxies that, during a request, refer to the objects of that request. They eliminate the need to pass the query from one function to another, but can only be used while processing a query (or within a manually created application context);
- the decorators of [Python] serve the same purpose as the annotations of [Java]: routes, tags ([@public]), and access controls ([@roles_required]). But a decorator is simply a function that you can write yourself in just a few lines;
- [Python] is a dynamically typed language: type annotations ([list[Doctor]], [Protocol], generic classes) document the code and guide the editor, but are not checked at runtime. Tools such as [Pylance] (in [Visual Studio Code]) or [mypy] check them before execution;
- [SQLAlchemy] requires writing the DAO layer (a few lines per request), but its requests—constructed using [Python] objects—remain close to the SQL they produce, and displaying the SQL ([SQL_ECHO]) commands allows you to verify what is being sent to the database. Loading relations is explicitly controlled, query by query;
- The [Jinja2] views mix HTML and [{{ }}] / [{% %}] tags: they are not valid HTML pages like those of [Thymeleaf], but their language, similar to [Python], is powerful (filters, macros, template inheritance), and automatic escaping protects against HTML injection.
The case study shows that a classic HTML application, without the essential [JavaScript], remains a simple and robust solution: each page is a URL, the browser’s “Back” button works, and the server controls everything. The limited use of [JavaScript] (confirmation windows) enhances the user experience without being necessary for the application to function.
For more information, you can consult the documentation for [Flask] (flask.palletsprojects.com), [Jinja2] (jinja.palletsprojects.com), [Werkzeug] (werkzeug.palletsprojects.com), [WTForms] (wtforms.readthedocs.io), and [Flask-WTF] (flask-wtf.readthedocs.io), from [SQLAlchemy] (docs.sqlalchemy.org), from [Pydantic] (docs.pydantic.dev), and compare this application with its counterparts [Spring MVC], [ASP.NET Core MVC], and [NestJS].