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

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомных Velo (Wix Code) решений
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

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

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

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

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

Client-side код (страницы и мастер-страница):

  • Файлы страниц: pages/myPage.js
  • Мастер-страница: masterPage.js
  • Публичные модули: public/utils.js (переиспользуемый код)
  • Доступ к DOM через $w() API (аналог jQuery-селекторов)

Backend код (serverless, Node.js):

  • Web Methods: backend/myService.web.js — вызываются с клиента как функции
  • HTTP Functions: backend/http-functions.js — REST API эндпоинты
  • Jobs: backend/jobs.config.json — scheduled tasks (cron)
  • Hooks коллекций: 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          # collection hooks
    └── 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 endpoint
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();
  }
}

Collection Hooks — бизнес-логика при работе с данными

// 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-кода (секреты защищены):

// 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 — не в коде.

Scheduled Jobs (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 });
  }
}

Типичные сроки

Кастомная форма с валидацией, backend-обработкой и Telegram/CRM-интеграцией — 2–3 рабочих дня. Полноценный каталог с фильтрацией, пагинацией, динамическими страницами и API — 7–12 дней. Личный кабинет с регистрацией, профилем, историей заказов — 2–3 недели.