Database Migrations Development for Web Application

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Database Migrations Development for Web Application
Medium
from 1 business day to 3 business days
FAQ

Our competencies:

Development stages

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1171
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    831
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    879
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    453

Database Migrations Development for Web Applications

Migrations are a managed way to change database schema along with code. Without them, schema changes are deployed manually, get lost in Slack, or cause production incidents.

Tools

Choice depends on your stack:

Tool Stack Format
Flyway Java, any SQL
Liquibase Java, any XML/YAML/SQL
Alembic Python/SQLAlchemy Python
golang-migrate Go, any SQL
Laravel Migrations PHP/Laravel PHP
Rails Migrations Ruby/Rails Ruby
Knex Node.js JS
Prisma Migrate Node.js/TypeScript Prisma schema

Universal option — golang-migrate or Flyway with plain SQL: independent of ORM, work from CI without the application, migrations are readable without ORM knowledge.

Migration Writing Principles

Each migration is atomic and reversible. If you can't write a correct down, it signals that the migration does too much.

Production migrations are never edited. If an error was applied — write a new migration.

No data migrations in schema migrations. Data migration is a separate script.

Example with golang-migrate

migrate create -ext sql -dir db/migrations -seq add_search_vector_to_products

Creates two files: 000003_add_search_vector_to_products.up.sql and ...down.sql.

-- 000003_add_search_vector_to_products.up.sql

BEGIN;

ALTER TABLE products
    ADD COLUMN IF NOT EXISTS search_vector TSVECTOR;

UPDATE products
SET search_vector = to_tsvector('russian', coalesce(title, '') || ' ' || coalesce(description, ''));

CREATE INDEX CONCURRENTLY idx_products_search ON products USING GIN (search_vector);

CREATE OR REPLACE FUNCTION products_search_vector_update() RETURNS TRIGGER AS $$
BEGIN
    NEW.search_vector := to_tsvector('russian',
        coalesce(NEW.title, '') || ' ' || coalesce(NEW.description, '')
    );
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER products_search_vector_trigger
    BEFORE INSERT OR UPDATE ON products
    FOR EACH ROW EXECUTE FUNCTION products_search_vector_update();

COMMIT;
-- 000003_add_search_vector_to_products.down.sql

BEGIN;
DROP TRIGGER IF EXISTS products_search_vector_trigger ON products;
DROP FUNCTION IF EXISTS products_search_vector_update();
DROP INDEX IF EXISTS idx_products_search;
ALTER TABLE products DROP COLUMN IF EXISTS search_vector;
COMMIT;

CREATE INDEX CONCURRENTLY can't run inside a transaction. For such migrations, wrap in a separate step without BEGIN/COMMIT, or use Flyway with executeInTransaction = false.

Zero-Downtime Migrations

Main rule: each migration must be compatible with the previous and next code version simultaneously.

Because deployment looks like: migration runs first, then new application instances start, old ones gradually shut down. At this moment both code generations work simultaneously.

Adding a column:

-- Safe: nullable without DEFAULT or with constant
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Unsafe in old PostgreSQL: NOT NULL with DEFAULT — table rewrite
-- Safe in PostgreSQL 11+: NOT NULL with DEFAULT constant (no rewrite)
ALTER TABLE users ADD COLUMN is_verified BOOLEAN NOT NULL DEFAULT false;

Renaming a column — always in 3 stages:

Deploy 1: add new column, code writes to both
Deploy 2: migrate data, code reads from new
Deploy 3: drop old column

Changing column type:

-- PostgreSQL 12+: instant for varchar(n) -> varchar(m) when m > n
-- table rewrite for most other type changes

-- Safe path: add new, copy, switch
ALTER TABLE products ADD COLUMN price_cents BIGINT;
UPDATE products SET price_cents = (price * 100)::BIGINT;
-- next code deploy: read price_cents
-- next migration: DROP COLUMN price

Dropping a column:

-- First code stops reading/writing column (deploy)
-- Then migration
ALTER TABLE products DROP COLUMN old_field;

Versioning and Naming

Good name format: {timestamp}_{verb}_{object}_{detail}.

20240315_001_create_users.sql
20240315_002_add_email_index_to_users.sql
20240320_001_add_status_to_products.sql
20240401_001_create_orders.sql

Timestamp-based versions (like in Flyway) are better than sequence-based (Alembic --autogenerate) when multiple developers work in parallel: no version conflicts.

CI/CD Integration

# .github/workflows/deploy.yml (fragment)
- name: Run migrations
  run: |
    migrate -path db/migrations \
            -database "$DATABASE_URL" \
            -verbose up
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

- name: Deploy application
  run: ./deploy.sh

Migrations apply before new code deployment. Rollback: migrate down N — rollback N latest migrations.

Flyway for Java/Any Stack

<!-- pom.xml -->
<plugin>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-maven-plugin</artifactId>
    <configuration>
        <url>${DB_URL}</url>
        <user>${DB_USER}</user>
        <password>${DB_PASSWORD}</password>
        <locations>classpath:db/migration</locations>
        <outOfOrder>false</outOfOrder>
        <validateOnMigrate>true</validateOnMigrate>
    </configuration>
</plugin>
mvn flyway:migrate
mvn flyway:info      # status of all migrations
mvn flyway:validate  # check checksums

Flyway stores a checksum of each migration in the flyway_schema_history table. If the file changes after application — validate throws an error.

Timelines

Setting up migration infrastructure (tool, naming, CI step, rollback procedure) for a new project: half a day. Writing a set of initial migrations for an existing schema (reverse engineering): 1 day.