├── requirements-dev.txt ├── requirements.txt ├── app ├── models.py ├── utils.py ├── __init__.py ├── config.py ├── api.py └── configure_app.py ├── resources ├── app.yml ├── package.yml └── customs.yml ├── main_app.py ├── pyproject.toml ├── serverless.yml ├── .pre-commit-config.yaml ├── Makefile ├── README.md └── .gitignore /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pre-commit 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.1.2 2 | Werkzeug==1.0.1 3 | -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | """Create database (ORM) models here""" 2 | -------------------------------------------------------------------------------- /app/utils.py: -------------------------------------------------------------------------------- 1 | """ Create your utility functions on this module""" 2 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | from .configure_app import get_application 3 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | """Flask configuration variables.""" 2 | import os 3 | 4 | 5 | class Config: 6 | ENVIRONMENT = os.environ["env"] 7 | SECRET_KEY = os.environ["SECRET_KEY"] 8 | -------------------------------------------------------------------------------- /app/api.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify 2 | 3 | router = Blueprint("router", __name__) 4 | 5 | 6 | @router.route("/hello", methods=["GET"]) 7 | def get_hello(): 8 | return jsonify({"message": "Hello world"}) 9 | -------------------------------------------------------------------------------- /resources/app.yml: -------------------------------------------------------------------------------- 1 | # Map all the events from api gateway to the flask app 2 | app: 3 | # Handler is necessary to make requests compatible 4 | # https://www.serverless.com/plugins/serverless-wsgi 5 | handler: wsgi_handler.handler 6 | description: "" 7 | events: 8 | - http: ANY / 9 | - http: 'ANY {proxy+}' 10 | -------------------------------------------------------------------------------- /main_app.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import jsonify 4 | 5 | from app import get_application 6 | 7 | app = get_application() 8 | 9 | 10 | @app.route("/") 11 | def get_root(): 12 | print(app.config) 13 | return jsonify( 14 | { 15 | "message": f"Environment:{os.environ['env']} - and secret key - {os.environ['SECRET_KEY']}" 16 | } 17 | ) 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 88 3 | target-version = ['py36', 'py37', 'py38'] 4 | include = '\.pyi?$' 5 | extend-exclude = ''' 6 | /( 7 | # The following are specific to Black, you probably don't want those. 8 | | blib2to3 9 | | tests/data 10 | | profiling 11 | )/ 12 | ''' 13 | exclude = ''' 14 | /( 15 | \.git 16 | | \.hg 17 | | \.mypy_cache 18 | | \.tox 19 | | \.venv 20 | | _build 21 | | buck-out 22 | | build 23 | | dist 24 | )/ 25 | ''' 26 | 27 | [build-system] 28 | requires = ["setuptools>=41.0", "setuptools-scm", "wheel"] 29 | build-backend = "setuptools.build_meta" 30 | -------------------------------------------------------------------------------- /resources/package.yml: -------------------------------------------------------------------------------- 1 | # Specify the modules that you dont want to include into the application as lambda is very limited to size 2 | # You can also use other methods to include or exclude modules: 3 | # https://www.serverless.com/framework/docs/providers/aws/guide/packaging/ 4 | exclude: 5 | - resources/** 6 | - .idea/** 7 | - __pycache__/ 8 | - .serverless 9 | - static/** 10 | - node_modules/** 11 | - venv/** 12 | - .pytest_cache 13 | - .serverless-offline 14 | - .env 15 | - requirements.txt 16 | - tests/** 17 | - README.md 18 | - package.json 19 | - package-lock.json 20 | - Makefile 21 | -------------------------------------------------------------------------------- /app/configure_app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | """ 4 | - Initialize main instance of the application 5 | - Initialize the extensions that you'll use around the app: 6 | ex: 7 | db = SQLAlchemy() 8 | ma = Marshmallow() 9 | """ 10 | 11 | 12 | def get_application(): 13 | app = Flask(__name__) 14 | app.config.from_object("app.config.Config") # configure app from config.py 15 | """ 16 | Other configurations 17 | db.init_app(app) 18 | CORS(app) 19 | CognitoAuth(app) 20 | """ 21 | with app.app_context(): 22 | from app.api import router # Import the api 23 | app.register_blueprint(router) # Register to the main app instance 24 | return app 25 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-flask # 2 | frameworkVersion: '2' 3 | 4 | custom: ${file(resources/customs.yml)} 5 | 6 | provider: 7 | name: aws 8 | stage: ${opt:stage, 'dev'} 9 | runtime: python3.8 # Python runtime 10 | region: us-east-1 # Region to deploy 11 | stackName: sls-flask-stack-${self:provider.stage} # Cloudformation stack.(Change this) 12 | lambdaHashingVersion: 20201221 13 | apiGateway: 14 | shouldStartNameWithService: true 15 | environment: # Environment Variables 16 | env: ${self:custom.ENVIRONMENT.${self:provider.stage}} 17 | API_GATEWAY_BASE_PATH: ${self:custom.customDomain.basePath} # This is necessary 18 | SECRET_KEY: ${self:custom.SECRET_KEY.${self:provider.stage}} 19 | package: ${file(resources/package.yml)} # Package reference 20 | 21 | functions: 22 | - ${file(resources/app.yml)} # Function reference 23 | 24 | plugins: 25 | - serverless-python-requirements 26 | - serverless-domain-manager 27 | - serverless-wsgi 28 | - serverless-stage-manager 29 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.2.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: check-added-large-files 9 | - id: detect-aws-credentials 10 | - id: detect-private-key 11 | - repo: https://github.com/pycqa/isort 12 | rev: 5.5.4 13 | hooks: 14 | - id: isort 15 | args: ["--profile", "black"] 16 | 17 | - repo: https://github.com/psf/black 18 | rev: 19.10b0 # Replace by any tag/version: https://github.com/psf/black/tags 19 | hooks: 20 | - id: black 21 | language_version: python3 # Should be a command that runs python3.6+t runs python3.6+ 22 | 23 | - repo: https://github.com/myint/autoflake 24 | rev: v1.4 25 | hooks: 26 | - id: autoflake 27 | args: 28 | - --in-place 29 | - --remove-all-unused-imports 30 | - --expand-star-imports 31 | - --remove-duplicate-keys 32 | - --remove-unused-variables 33 | - --exclude=__init__.py 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRCPATH := $(CURDIR) 2 | PROJECTNAME := $(shell basename $(CURDIR)) 3 | 4 | define HELP 5 | Manage $(PROJECTNAME). Usage: 6 | - - - - - - - - - - - - - - - - - - - - - - - - - 7 | make pre-commit - Run pre-commit hooks manually 8 | make deploy-prod - Deploy the function to prod environment 9 | make deploy-dev - Deploy the function to dev environment 10 | make venv - Create a fresh virtualenv 11 | make check-venv - Verifies the virtualenv 12 | make install - Creates a new virtualenv and installs the packages 13 | make clean - Clean the virutalenv and other pyc files 14 | endef 15 | export HELP 16 | 17 | .PHONY: pre-commit 18 | pre-commit: 19 | pre-commit run --all-files 20 | 21 | .PHONY: deploy-prod 22 | deploy-prod: 23 | sls deploy -s prod 24 | 25 | .PHONY: deploy-dev 26 | deploy-dev: 27 | sls deploy -s dev 28 | 29 | 30 | .PHONY: remove-dev 31 | remove-dev: 32 | sls remove -s dev 33 | 34 | .PHONY: remove-prod 35 | remove-prod: 36 | sls remove -s prod 37 | 38 | 39 | venv: 40 | virtualenv venv 41 | 42 | check-venv: 43 | . venv/bin/activate && pip -V && 44 | 45 | install: venv 46 | . venv/bin/activate && pip install -r requirements.txt && pip install -r requirements-dev.txt 47 | 48 | clean: 49 | rm -rf venv 50 | find -iname "*.pyc" -delete 51 | 52 | all help: 53 | @echo "$$HELP" 54 | -------------------------------------------------------------------------------- /resources/customs.yml: -------------------------------------------------------------------------------- 1 | region: ${self:provider.region} 2 | # Define stages for the function. 3 | # https://github.com/jeremydaly/serverless-stage-manager 4 | # Define stages on the >custom key< 5 | stages: 6 | - dev 7 | - prod 8 | # wsgi serverless plugin 9 | # https://github.com/logandk/serverless-wsgi 10 | wsgi: 11 | app: main_app.app # Where the flask instance is located 12 | packRequirements: false 13 | # Serverless plugin to package the application (pythonRequirements) 14 | # In this case docker is being used to package the application. This is necessary when you have packages 15 | # like numpy or pandas.. With the plugin you can use different methods for packaging 16 | # (layers, from requirements.txt, docker, etc) https://github.com/UnitedIncome/serverless-python-requirements 17 | pythonRequirements: 18 | dockerizePip: non-linux 19 | # Here the python-state-manager plugin is being used to redirect the trafic to a specific domain 20 | # https://github.com/amplify-education/serverless-domain-manager 21 | customDomain: 22 | domainName: ${self:custom.domains.${self:provider.stage}} # Get the domain based on the stage 23 | basePath: ${self:custom.basePath.${self:provider.stage}} # Get the basepath based on the stage 24 | createRoute53Record: true 25 | # Add custom domain names to separate based on the environment 26 | domains: 27 | prod: "flask.xypsylon.xyz" 28 | dev: "flaskdev.xypsylon.xyz" 29 | basePath: 30 | prod: "flask-serverless" 31 | dev: "flask-serverless" 32 | ENVIRONMENT: 33 | prod: "PRODUCTION" 34 | dev: "DEVELOPMENT" 35 | SECRET_KEY: 36 | prod: "SECRET_KEY_PROD" 37 | dev: "SECRET_KEY_DEV" 38 | 39 | # Get keys from secret manager 40 | #GOOGLE_MAPS_API_KEY: 41 | # prod: ${ssm:/google_maps_api_key~true} 42 | # dev: ${ssm:/google_maps_api_key_dev~true} 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-serverless-boilerplate 2 | ![Serverless](https://i.imgur.com/2QqTjAb.png) 3 | 4 | ## Usage 5 | Clone the project: 6 | 7 | ```bash 8 | git clone https://github.com/vjanz/flask-serverless-boilerplate 9 | ``` 10 | 11 | Get inside the project and make changes to: 12 | - serverless.yml (change service name or env variables) 13 | - `resources/customs.yml` make changes to env variables or other configuration like packaging method 14 | - api.py add new rotues or if you add a new module don't forget to import in configure_app.py 15 | - add database models at app/models.py 16 | - add utility functions at utils.py 17 | 18 | 19 | #### Install local development 20 | ```makefile 21 | make install 22 | ``` 23 | This command will: 24 | - Create a virtual environment 25 | - Install dependencies on that environment 26 | 27 | You can do these steps manually if you wish to. (ex, make command won't work in windows, but for linux based it should be fine) 28 | 29 | #### Register new environment variables 30 | Add the environment variables at `customs.yml` or `severless.yml` then specify with the same key at `config.py`. Then they'll be autoloaded in your application config `app.config`. 31 | 32 | #### Register new dependencies 33 | Install the dependencies and then export the runtime dependencies at `requirements.txt` so serverless can get them. 34 | 35 | 36 | 37 | 38 | 39 | #### Check for formatting/linting 40 | ```makefile 41 | make pre-commit 42 | ``` 43 | #### Clean virtualenv and other files: 44 | ```makefile 45 | make clean 46 | ``` 47 | 48 | 49 | ## Deploy 50 | #### Deploy to production: 51 | ```makefile 52 | make deploy-prod 53 | ``` 54 | 55 | #### Deploy to dev: 56 | ```makefile 57 | make deploy-dev 58 | ``` 59 | 60 | #### TODO 61 | Implement CI/CD with Github actions 62 | 63 | ## License 64 | 65 | This project is licensed under the terms of the MIT license. 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | .idea 8 | venv 9 | env 10 | .env 11 | .serverless/** 12 | node_modules/** 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | cover/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | .pybuilder/ 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | 91 | # pyenv 92 | # For a library or package, you might want to ignore these files since the code is 93 | # intended to run in multiple environments; otherwise, check them in: 94 | # .python-version 95 | 96 | # pipenv 97 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 98 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 99 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 100 | # install all needed dependencies. 101 | #Pipfile.lock 102 | 103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 104 | __pypackages__/ 105 | 106 | # Celery stuff 107 | celerybeat-schedule 108 | celerybeat.pid 109 | 110 | # SageMath parsed files 111 | *.sage.py 112 | 113 | # Environments 114 | .env 115 | .venv 116 | env/ 117 | venv/ 118 | ENV/ 119 | env.bak/ 120 | venv.bak/ 121 | 122 | # Spyder project settings 123 | .spyderproject 124 | .spyproject 125 | 126 | # Rope project settings 127 | .ropeproject 128 | 129 | # mkdocs documentation 130 | /site 131 | 132 | # mypy 133 | .mypy_cache/ 134 | .dmypy.json 135 | dmypy.json 136 | 137 | # Pyre type checker 138 | .pyre/ 139 | 140 | # pytype static type analyzer 141 | .pytype/ 142 | 143 | # Cython debug symbols 144 | cython_debug/ 145 | --------------------------------------------------------------------------------