<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Stefan Meisner Larsen</title>
    <description>The latest articles on Forem by Stefan Meisner Larsen (@stefan_meisnerlarsen_f5d).</description>
    <link>https://forem.com/stefan_meisnerlarsen_f5d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2210677%2F86a749dd-2e4c-4a5e-aa73-b3b2c4ca08bb.png</url>
      <title>Forem: Stefan Meisner Larsen</title>
      <link>https://forem.com/stefan_meisnerlarsen_f5d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/stefan_meisnerlarsen_f5d"/>
    <language>en</language>
    <item>
      <title>Single Page Web App in Python - Part 1</title>
      <dc:creator>Stefan Meisner Larsen</dc:creator>
      <pubDate>Sat, 15 Nov 2025 11:54:00 +0000</pubDate>
      <link>https://forem.com/stefan_meisnerlarsen_f5d/single-page-web-app-in-python-part-1-4nch</link>
      <guid>https://forem.com/stefan_meisnerlarsen_f5d/single-page-web-app-in-python-part-1-4nch</guid>
      <description>&lt;h2&gt;
  
  
  Why this?
&lt;/h2&gt;

&lt;p&gt;I found myself working on yet another python web application, copying some stuff from the previous one, improving it a bit and wondering if now is the time to create that great example project to make life easier next time.&lt;br&gt;
I am starting this without knowing exactly where it will end, but I imagine a basic single page application with complete build and installation scripts for Linux, Docker and Kubernetes.&lt;/p&gt;
&lt;h2&gt;
  
  
  In this post
&lt;/h2&gt;

&lt;p&gt;In this post I will create a small python project that can render a page in a browser - like Hello World for web apps. This will be the foundation for the next, soon to follow post. Stay tuned!&lt;/p&gt;
&lt;h2&gt;
  
  
  Source on GitHub
&lt;/h2&gt;

&lt;p&gt;Source for this post in on branch post_1 on &lt;a href="https://github.com/stefanmeisner/single_page_python" rel="noopener noreferrer"&gt;https://github.com/stefanmeisner/single_page_python&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Here we go!
&lt;/h2&gt;

&lt;p&gt;There are a number of web frameworks for python out there. I have stumbled into &lt;a href="https://flask.palletsprojects.com/en/stable/" rel="noopener noreferrer"&gt;Flask&lt;/a&gt;  a number of times. It's quite minimalistic compared to for example Django, and you're up and running in a very few lines of code.&lt;/p&gt;

&lt;p&gt;Flask comes along with &lt;a href="https://jinja.palletsprojects.com/en/stable/" rel="noopener noreferrer"&gt;Jinja2&lt;/a&gt; templating engine], another of my favorite tools.&lt;/p&gt;
&lt;h3&gt;
  
  
  Project layout
&lt;/h3&gt;

&lt;p&gt;First things first - I have googled around and experimented too, until I settled with this project layout, which is quite common I believe.&lt;br&gt;
If you are used to setup.py and requirements.txt this may be new for you. Welcome to the world of pyproject.toml!&lt;/p&gt;

&lt;p&gt;I have found the following project layout to work well for me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
  single_page_python/
    README.md
    LICENSE
    .gitignore
    pyproject.toml
    src/
      single_page_python.py
      single_page_python/
        __init__.py
        server.py
        templates/
          index.html.j2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick walk-through of the project files
&lt;/h3&gt;

&lt;h4&gt;
  
  
  pyproject.toml
&lt;/h4&gt;

&lt;p&gt;I have not experimented with other build systems than setuptools, it has worked well for me so far. Dependencies goes into "dependencies" in the project section, and in the last section there is a list of development dependencies, required to build the project. This is also where you would add pytest and other stuff that should not be part of the shipped application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[build-system]&lt;/span&gt;
&lt;span class="py"&gt;requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"setuptools &amp;gt;= 80"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"setuptools-scm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"wheel"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;build-backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"setuptools.build_meta"&lt;/span&gt;

&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"single_page_python"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.0.1"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Single Page Web Application with Python and JavaScript"&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="py"&gt;{name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Stefan Meisner Larsen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"developer@meisner-larsen.dk"&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;readme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"README.md"&lt;/span&gt;
&lt;span class="py"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.11&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"flask"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[project.optional-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  server.py - the interesting stuff :)
&lt;/h4&gt;

&lt;p&gt;We start by creating a Flask app - an instance of the class Flask, stating the name of our module as argument.&lt;/p&gt;

&lt;p&gt;Using the app.route as decorator, we can easily specify, that any request for '/' should be handled by the index() method.&lt;/p&gt;

&lt;p&gt;The render_template() function takes the name of a jinja2 template located in 'templates' folder, relative to server.py.&lt;/p&gt;

&lt;p&gt;The 'title' argument is a context variable, that will be passed to the jinja template.&lt;br&gt;
Let's take a look at that!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Copyright 2025 Stefan Meisner Larsen
# Licensed under the MIT License.
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;index.html.j2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Single Page Web Application&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_server&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  templates/index.html.j2
&lt;/h4&gt;

&lt;p&gt;The naming of this file, with double extension, makes it possible to provide syntax highlighting for both Jinja and HTML (at least in vscode, I did not get it to work here). Look how the 'title' argument to render_template() is being used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h5&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h5&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  single_page_python.py - Where it all begins
&lt;/h4&gt;

&lt;p&gt;This is the application entry point, the python file that you will actually be executing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;single_page_python.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;start_server&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;start_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a virtual environment for our project
&lt;/h3&gt;

&lt;p&gt;Before installing any packages, we will create a virtual python environment ".venv" in the project root. The virtual environment is an isolated python installation, This will ensure that we don't tamper with the global python installation. On my linux system, the global python interpreter is called python3. We use the global interpreter to create the virtual environment.&lt;br&gt;
After creating the environment, don't forget to activate it. This is done by "sourcing" the activate script using ".":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;.&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the application
&lt;/h3&gt;

&lt;p&gt;In the project root (where pyproject.tom is) install the application and the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'.[dev]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python src/single_page_python.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try it out here &lt;a href="http://localhost:5000" rel="noopener noreferrer"&gt;http://localhost:5000&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>flask</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
