Розробка кастомних Velo (Wix Code) рішень

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомних Velo (Wix Code) рішень
Середня
~3-5 робочих днів
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка користувацьких розв'язань Velo (Wix Code)

Velo by Wix — JavaScript-платформа на основі Wix, яка надає доступ до повноцінного програмування: робота з даними через API коллекцій, функції serverless (HTTP Functions, Web Methods), маршрутизація, OAuth, зовнішні API. Це не «трохи коду» в конструкторі — це середовище розробки з власними паттернами, обмеженнями та можливостями, недоступними через візуальний редактор.

Архітектура Velo

Velo розділена на два контексти:

Код на клієнтській стороні (сторінки та головна сторінка):

  • Файли сторінок: pages/myPage.js
  • Головна сторінка: masterPage.js
  • Публічні модулі: public/utils.js (код, що повторно використовується)
  • Доступ до DOM через $w() API (селектори, як у jQuery)

Код бекенду (serverless, Node.js):

  • Web Methods: backend/myService.web.js — викликаються з клієнта як функції
  • HTTP Functions: backend/http-functions.js — REST API точки входу
  • Jobs: backend/jobs.config.json — запланов задачи (cron)
  • Гаки коллекцій: backend/data.js — beforeInsert, afterQuery тощо
src/
├── pages/
│   ├── home.js
│   └── catalog.js
├── masterPage.js
├── public/
│   ├── utils.js
│   └── constants.js
└── backend/
    ├── http-functions.js
    ├── myService.web.js
    ├── data.js          # гаки коллекцій
    └── jobs.config.json

HTTP Functions — зовнішній API

HTTP Functions дозволяють створювати публічні REST точки входу на домені сайту: https://domain.com/_functions/myEndpoint.

// backend/http-functions.js
import { ok, badRequest, serverError } from 'wix-http-functions';
import wixData from 'wix-data';

export async function get_products(request) {
  try {
    const { query } = request;
    const category = query.category;

    let dbQuery = wixData.query('Products')
      .eq('isActive', true);

    if (category) {
      dbQuery = dbQuery.eq('category', category);
    }

    const result = await dbQuery
      .ascending('order')
      .find({ suppressAuth: true });

    return ok({
      body: JSON.stringify({
        items: result.items,
        total: result.totalCount,
      }),
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'public, max-age=300',
      }
    });
  } catch (error) {
    return serverError({ body: JSON.stringify({ error: error.message }) });
  }
}

// POST точка входу
export async function post_leads(request) {
  const body = await request.body.json();

  // Валідація
  if (!body.email || !body.name) {
    return badRequest({ body: JSON.stringify({ error: 'Missing required fields' }) });
  }

  // Honeypot
  if (body.website) {
    return ok({ body: JSON.stringify({ success: true }) }); // мовчки ігноруємо бота
  }

  await wixData.insert('Leads', {
    name: body.name,
    email: body.email,
    message: body.message,
    source: body.source ?? 'direct',
    createdAt: new Date(),
  });

  // Відправити до CRM через HTTP
  await fetch(process.env.CRM_WEBHOOK, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });

  return ok({ body: JSON.stringify({ success: true }) });
}

Web Methods — безпечні виклики бекенду з типами

Web Methods — бажаний спосіб виклику серверного коду з клієнта в межах Velo:

// backend/catalogService.web.js
import { Permissions, webMethod } from 'wix-web-module';
import wixData from 'wix-data';

export const getFilteredProducts = webMethod(
  Permissions.Anyone,
  async ({ category, minPrice, maxPrice, page = 1, limit = 12 }) => {
    let query = wixData.query('Products')
      .eq('isActive', true)
      .ge('price', minPrice ?? 0);

    if (maxPrice) query = query.le('price', maxPrice);
    if (category) query = query.eq('category', category);

    const result = await query
      .ascending('order')
      .skip((page - 1) * limit)
      .limit(limit)
      .find();

    return {
      items: result.items,
      total: result.totalCount,
      pages: Math.ceil(result.totalCount / limit),
    };
  }
);
// pages/catalog.js
import { getFilteredProducts } from 'backend/catalogService.web';

$w.onReady(function () {
  loadProducts({ category: null, page: 1 });
});

async function loadProducts(filters) {
  $w('#loadingSpinner').show();

  try {
    const { items, total, pages } = await getFilteredProducts(filters);
    $w('#repeater').data = items;
    $w('#totalCount').text = `Знайдено: ${total}`;
    updatePagination(pages, filters.page);
  } finally {
    $w('#loadingSpinner').hide();
  }
}

Гаки коллекцій — бізнес-логіка при роботі з даними

// backend/data.js
import { triggered } from 'wix-data';

// Автоматично генерувати slug перед вставленням
export function beforeInsert_Products(item, context) {
  if (!item.slug) {
    item.slug = item.title
      .toLowerCase()
      .replace(/[^а-яёa-z0-9]/gi, '-')
      .replace(/-+/g, '-');
  }
  item.createdAt = new Date();
  return item;
}

// Логування змін
export async function afterUpdate_Products(item, context) {
  await wixData.insert('AuditLog', {
    entityType: 'Products',
    entityId: item._id,
    action: 'update',
    userId: context.userId,
    timestamp: new Date(),
  });
  return item;
}

// Каскадне видалення пов'язаних записів
export async function beforeRemove_Categories(item, context) {
  const products = await wixData.query('Products')
    .eq('category', item._id)
    .find({ suppressAuth: true });

  if (products.totalCount > 0) {
    throw new Error(`Не можна видалити категорію з ${products.totalCount} товарами`);
  }
  return item;
}

Маршрутизація та користувацькі URL

// routers.js — користувацький маршрутизатор для /catalog/*
export async function catalog_Router(request) {
  const slug = request.path[0];

  if (!slug) {
    return WixRouterSitemapEntry('/catalog');
  }

  const result = await wixData.query('Products')
    .eq('slug', slug)
    .eq('isActive', true)
    .find({ suppressAuth: true });

  if (result.items.length === 0) {
    return notFound();
  }

  return ok('catalog-product', {
    product: result.items[0],
    relatedItems: [], // додаткові дані для сторінки
  });
}

Інтеграція з зовнішніми API

Виклики зовнішніх API — тільки з коду бекенду (секрети захищені):

// backend/integrations.web.js
import { webMethod, Permissions } from 'wix-web-module';
import { getSecret } from 'wix-secrets-backend';

export const sendToTelegram = webMethod(
  Permissions.Anyone,
  async (message) => {
    const botToken = await getSecret('TELEGRAM_BOT_TOKEN');
    const chatId = await getSecret('TELEGRAM_CHAT_ID');

    await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chat_id: chatId,
        text: message,
        parse_mode: 'HTML',
      })
    });
  }
);

Секрети зберігаються через Wix Secrets Manager — не в коді.

Запланов завдання (Cron)

// backend/jobs.config.json
{
  "jobs": [
    {
      "functionLocation": "/cleanupService",
      "functionName": "cleanupOldLeads",
      "executionConfig": {
        "cronExpression": "0 3 * * *"
      }
    }
  ]
}
// backend/cleanupService.js
import wixData from 'wix-data';

export async function cleanupOldLeads() {
  const thirtyDaysAgo = new Date();
  thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);

  const oldLeads = await wixData.query('Leads')
    .lt('createdAt', thirtyDaysAgo)
    .eq('status', 'processed')
    .find({ suppressAuth: true });

  for (const lead of oldLeads.items) {
    await wixData.remove('Leads', lead._id, { suppressAuth: true });
  }
}

Типові терміни

Користувацька форма з валідацією, обробкою бекенду та інтеграцією Telegram/CRM — 2–3 робочих дні. Повнофункціональний каталог з фільтруванням, пагінацією, динамічними сторінками та API — 7–12 днів. Особистий кабінет з реєстрацією, профілем, історією замовлень — 2–3 тижні.