Usando #flask como servidor de um app #Angular. (#python, #js)

Usando #flask como servidor de um app #Angular. (#python, #js)

Overview

Já pensou em dar vida ao seu frontend Angular através de uma API Python+Flask sem complicações? Neste post, detalharei um método simples e eficaz de utilizar o Flask como servidor para sua aplicação Angular, empregando uma estratégia ‘catch-all’ que promete simplificar significativamente o processo. Se você, assim como eu, preza por soluções práticas (que talvez um dia chamamos de ‘preguiça’), então este guia é para ti. Mergulhe conosco nessa jornada de integração entre Angular e Flask!

Outro dia resolvi criar um frontend em angular para uma api que havia feito em python + flask. Por mera preguiça conveniência, resolvi colocar o Flask para ser o servidor do frontend. É bem simples e exige apenas a rota “catch all” com algumas modificações.

  • O que é esta rota “catch all”? É um snippet feito pelo próprio pessoal do Flask, que captura todas as requests e as processa. Foi feito exatamente SPA (single page applications).
  • Como as rotas da API estão organizadas? As rotas relativas a api em si estão em um blueprint e organizadas a partir da url “/api/v1”. Todas as outras rotas serão utilizadas pelo frontend. Como o foco do post não é a funcionalidade de blueprints do Flask, não vou detalhar esta parte.

Vamos aos passos necessários…

Definindo diretório para arquivos estáticos

from flask import Flask

static_files_dir = "./templates/static"

app = Flask(__name__, static_folder=app_settings.FLASK_STATIC_FOLDER)

O primeiro passo é definir o diretório onde ficarão os arquivos do frontend. No código acima, escolhi o diretório “./templates/static” como local para salvar estes arquivos. Então, agora você deve criar este diretório (caso não exista) e copiar o fonte compilado do angular para este local.

Criando rota “catch all”

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    _path = path.lower().strip()

    if path in ["", "/", None]:
        return send_from_directory(static_files_dir, "index.html", mimetype="text/html")

    elif _path.endswith(".css"):
        return send_from_directory(static_files_dir, path, mimetype="text/css")

    elif _path.endswith(".js"):
        return send_from_directory(static_files_dir, path, mimetype="application/javascript")

    return send_from_directory(static_files_dir, path)

O código acima é uma adaptação do código feito pelo pessoal do Flask. O que precisei fazer foi separar o que está sendo solicitado na request e devolver o arquivo junto com o seu respectivo tipo (mimetype). Em versões antigas, isso não era necessário. Todavia, a partir da versão 8 ou 9 do Angular, isso é necessário.

Detalhando um pouco o que está acontecendo nesta rota:

  1. Nas linhas @app.route("/", defaults={“path”: “}) e @app.route(”/<path:path>"), estou definindo que todas as rotas vão ser direcionadas para este método. Não importa se você acessar o site sem definir um caminho (http://localhost:5000/) ou se tentar acessar um caminho tipo http://localhost:5000/dashboard ou http://localhost:5000/admin/login, todas estas requests vão cair neste mesmo método.
  2. _path = path.lower().strip(): O argumento path é o caminho que o usuário tentou acessar. Criei a variável _path para normalizar o que está chegando, ou seja, deixo todas as rotas em minúsculo e removo espaços extras.
  3. Nos ifs, estou verificando:
    1. Se o usuário entrou no site sem informar uma rota: Neste caso, retorno o index.html.
    2. Se a rota normalizada terminar com .css: Neste caso, retorno o arquivo CSS solicitado, incluindo o mimetype correto.
    3. Se a rota normalizada terminar com .js: Neste caso, , retorno o script JS solicitado, incluindo o mimetype correto.
    4. Qualquer outra rota, retorno o arquivo solicitado, mas nao informo um mimetype específico.

Esta rota “catch-all” não vai interferir com a api, pois, utilizando blueprint, já registrei as rotas iniciadas em /api/v1.

A aplicação Angular que eu fiz é bem simples, mas funcionou normalmente com esta abordagem.

Espero ter ajudado!