Build an industry selector for business onboarding
Search US NAICS 2022 titles with the Node SDK and keep the code and title a person selects in your onboarding form.
Working example
ParseAPISet up ParseAPI
Install the SDK in your application and set PARSEAPI_KEY in your server environment.
npm install @parseapi/sdk@1.14.0
export PARSEAPI_KEY='YOUR_SECRET_API_KEY'Create a client and reuse it for the calls below:
import { parseAPI } from '@parseapi/sdk';
const parse = parseAPI();Let a business choose its industry
Add an industry choice to a business onboarding form. Someone describes what the business does, reviews matching US NAICS 2022 titles, and selects the one that fits. Keep the selected code, title, country, and revision together.
This Node app keeps the secret key on the server and renders the choices in a normal browser form. Industry search has no Form.js preset, so this feature uses the existing SDK operation.
1. Search the stated activity
Read the activity description or code from your form. Pass it to Industry search:
const industries = await parse.industry.search(query, { limit: 6, signal });industries.results contains matching records. Use naics for the code and name for the title. The envelope supplies country: "US" and year: 2022 for the results. level identifies the classification depth, from a broad sector to a six-digit US industry.
The complete app accepts 2-100 characters and requests six choices per search. The server checks the returned country, revision, and record types before presenting them. An unavailable search remains a request error, not a claim that no industry matches.
2. Present the code beside its title
Build a button for each result:
for (const industry of industries.results) {
const item = document.createElement('li'), button = document.createElement('button');
const title = document.createElement('strong'), code = document.createElement('span');
button.type = 'button'; title.textContent = industry.name;
code.textContent = industry.naics + (industry.level < 6 ? ' · Broad classification' : ' · US industry');
button.append(title, code); button.addEventListener('click', () => choose(industry, industries));
item.append(button); results.append(item);
}The code helps distinguish similar titles. Broad results are labeled so the person can search more specifically when needed. Native buttons support keyboard selection. No result is selected automatically.
Searching by an activity is useful evidence for a person to review. It does not establish what a particular company does or decide its classification for a legal purpose.
3. Keep the confirmed choice with the form
When the person chooses a title, fill the fields your application will submit:
document.querySelector('#naics').value = industry.naics;
document.querySelector('#industry-name').value = industry.name;
document.querySelector('#industry-country').value = scope.country;
document.querySelector('#industry-year').value = String(scope.year);Treat naics as a string. Store its US country and 2022 revision with it so another part of the app can interpret the code later. Show the chosen title beside those details.
The complete app also emits an industrychange event from the form with that selected code, name, country, and year. Your existing onboarding code can consume those fields or handle the event. Editing the activity clears the old choice, so it cannot silently remain attached to a different description.
4. Recheck the choice when your app saves
Hidden inputs are still browser input. On your existing server-side save path, look up the submitted code with Industry and check its country and revision before storing the accepted selection. Keep the person's confirmation as part of your onboarding flow.
The example searches only on submit. Changing the activity cancels a pending search and clears its choices, and late answers cannot restore them. The SDK uses an eight-second timeout without retries for this interaction. See the Industry reference for code lookup, hierarchy and search behavior.
Complete example
Use the complete implementation as a reference when adding this feature to your app. It includes the validation and failure handling around the calls above.
View and copy the complete source
import { createServer } from 'node:http';
import { pathToFileURL } from 'node:url';
import { parseAPI } from '@parseapi/sdk';
export async function searchIndustries(query, parse, signal) {
query = typeof query === 'string' ? query.trim() : '';
if (query.length < 2 || query.length > 100) throw new Error('invalid_query');
// example:request
const industries = await parse.industry.search(query, { limit: 6, signal });
// /example:request
if (!industries || industries.country !== 'US' || industries.year !== 2022 ||
!Array.isArray(industries.results) || industries.results.length > 6 ||
industries.results.some(item => !item || typeof item.naics !== 'string' ||
!/^(?:[0-9]{2,6}|31-33|44-45|48-49)$/.test(item.naics) ||
typeof item.name !== 'string' || !item.name || item.name.length > 500 ||
!Number.isInteger(item.level) || item.level < 2 || item.level > 6)) throw new Error('unexpected_response');
return { country: industries.country, year: industries.year, results: industries.results.map(item => ({ naics: item.naics, name: item.name, level: item.level })) };
}
export const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Choose your business industry</title><style>
:root{color-scheme:light dark;--bg:#f5f6f8;--paper:#fff;--ink:#18212b;--muted:#66717f;--line:#dce1e8;--field:#f7f8fa;--accent:#2459cb;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}*{box-sizing:border-box}body{margin:0;padding:54px 20px;background:var(--bg);color:var(--ink)}main{max-width:680px;margin:auto;padding:36px;border:1px solid var(--line);border-radius:24px;background:var(--paper)}h1{font-size:clamp(28px,5vw,36px);line-height:1.15;letter-spacing:-.035em;margin:0 0 10px}p{font-size:15px;line-height:1.6;color:var(--muted)}label{display:block;font-size:14px;font-weight:600;margin:28px 0 8px}.search-row{display:flex;gap:10px}input[type=search]{width:100%;min-width:0;padding:12px 14px;border:1px solid var(--line);border-radius:11px;background:var(--field);color:var(--ink);font:inherit}button{min-height:46px;border:1px solid var(--line);border-radius:11px;padding:11px 18px;background:var(--paper);color:var(--ink);font:inherit;cursor:pointer}button[type=submit]{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}button:disabled{opacity:.55;cursor:wait}:focus-visible{outline:3px solid #80aaff;outline-offset:3px}#status{min-height:24px;margin:16px 0;font-size:14px}ul{list-style:none;padding:0;margin:0}li+li{margin-top:10px}li button{width:100%;text-align:left;padding:16px;line-height:1.5}li button:hover{border-color:var(--accent);background:var(--field)}li strong{display:block;font-weight:600}li span{display:block;color:var(--muted);font-size:13px;margin-top:4px}.selection{border-top:1px solid var(--line);padding-top:24px;margin-top:28px}.selection h2{font-size:13px;color:var(--muted);font-weight:500;margin:0 0 8px}.selection strong{font-size:20px;font-weight:600;letter-spacing:-.02em;line-height:1.4}.selection p{font-size:13px;margin:8px 0 0}.note{font-size:13px;margin:26px 0 0}[hidden]{display:none!important}@media(max-width:560px){body{padding:18px 12px}main{padding:24px 20px;border-radius:18px}.search-row{flex-wrap:wrap}.search-row button{width:100%}}@media(prefers-color-scheme:dark){:root{--bg:#0c1016;--paper:#141b24;--ink:#edf1f7;--muted:#a2afbf;--line:#303b49;--field:#1c2531;--accent:#2861db}}
</style></head><body><main><h1>What does your business do?</h1><p>Find and choose the US NAICS 2022 classification that fits your main business activity.</p>
<form id="industry-form"><label for="activity">Business activity or NAICS code</label><div class="search-row"><input id="activity" type="search" maxlength="100" autocomplete="off" placeholder="Try coffee shop or software development"><button id="search" type="submit">Find industries</button></div>
<input id="naics" type="hidden" name="naics"><input id="industry-name" type="hidden" name="industry_name"><input id="industry-country" type="hidden" name="industry_country"><input id="industry-year" type="hidden" name="industry_year"></form>
<p id="status" role="status" aria-live="polite">Search to see matching industry titles.</p><ul id="results" aria-label="Matching industries"></ul>
<section id="selection" class="selection" hidden><h2>Your selected industry</h2><strong id="selected-name"></strong><p id="selected-code"></p></section>
<p class="note">Choose the most specific title that fits. A classification is a business choice, not a company lookup or a legal determination.</p></main><script>
const form = document.querySelector('#industry-form'), activity = document.querySelector('#activity');
const results = document.querySelector('#results'), status = document.querySelector('#status'), search = document.querySelector('#search');
const selection = document.querySelector('#selection');
let request, revision = 0;
function clearSelection() {
for (const id of ['naics', 'industry-name', 'industry-country', 'industry-year']) document.querySelector('#' + id).value = '';
selection.hidden = true;
}
activity.addEventListener('input', () => { revision += 1; request?.abort(); results.replaceChildren(); clearSelection(); search.disabled = false; status.textContent = 'Search to find an industry for the edited activity.'; });
function choose(industry, scope) {
// example:selection
document.querySelector('#naics').value = industry.naics;
document.querySelector('#industry-name').value = industry.name;
document.querySelector('#industry-country').value = scope.country;
document.querySelector('#industry-year').value = String(scope.year);
// /example:selection
document.querySelector('#selected-name').textContent = industry.name;
document.querySelector('#selected-code').textContent = industry.naics + ' · ' + scope.country + ' NAICS ' + scope.year;
selection.hidden = false; results.replaceChildren(); status.textContent = 'Industry selected. Editing the activity clears this choice.';
form.dispatchEvent(new CustomEvent('industrychange', { bubbles: true, detail: { naics: industry.naics, name: industry.name, country: scope.country, year: scope.year } }));
}
form.addEventListener('submit', async event => {
event.preventDefault(); const query = activity.value.trim();
if (query.length < 2 || query.length > 100) { status.textContent = 'Enter 2 to 100 characters.'; return; }
request?.abort(); const controller = new AbortController(); request = controller; const current = ++revision;
clearSelection(); results.replaceChildren(); search.disabled = true; status.textContent = 'Finding matching industries.';
const timer = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch('/industries?q=' + encodeURIComponent(query), { signal: controller.signal, cache: 'no-store' });
const industries = await response.json();
if (current !== revision) return;
if (controller.signal.aborted) throw new Error('request_aborted');
if (!response.ok) throw new Error('request_failed');
// example:results
for (const industry of industries.results) {
const item = document.createElement('li'), button = document.createElement('button');
const title = document.createElement('strong'), code = document.createElement('span');
button.type = 'button'; title.textContent = industry.name;
code.textContent = industry.naics + (industry.level < 6 ? ' · Broad classification' : ' · US industry');
button.append(title, code); button.addEventListener('click', () => choose(industry, industries));
item.append(button); results.append(item);
}
// /example:results
status.textContent = industries.results.length ? 'Choose the title that describes your main activity.' : 'No matches. Try another description.';
} catch { if (current === revision) status.textContent = 'Industry search is unavailable. Try again.'; }
finally { clearTimeout(timer); if (current === revision) search.disabled = false; }
});
</script></body></html>`;
export function createAppServer(parse) {
return createServer(async (request, response) => {
response.setHeader('Cache-Control', 'no-store');
if (request.method !== 'GET') { response.writeHead(405, { Allow: 'GET' }); response.end(); return; }
const url = new URL(request.url, 'http://127.0.0.1');
if (url.pathname === '/') { response.setHeader('Content-Type', 'text/html; charset=utf-8'); response.end(PAGE); return; }
if (url.pathname !== '/industries') { response.writeHead(404); response.end(); return; }
response.setHeader('Content-Type', 'application/json');
const query = url.searchParams.get('q')?.trim() || '';
if (query.length < 2 || query.length > 100) { response.writeHead(400); response.end(JSON.stringify({ error: 'Enter 2 to 100 characters.' })); return; }
const controller = new AbortController();
response.once('close', () => { if (!response.writableEnded) controller.abort(); });
try { response.end(JSON.stringify(await searchIndustries(query, parse, controller.signal))); }
catch { if (!response.destroyed) { response.writeHead(502); response.end(JSON.stringify({ error: 'The industry lookup could not complete.' })); } }
});
}
function main() {
const port = Number(process.env.PORT || 8000);
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error('PORT must be between 1 and 65535.');
const key = process.env.PARSEAPI_KEY;
if (!key) throw new Error('Set PARSEAPI_KEY to your secret server key.');
const parse = parseAPI(key, { timeoutMs: 8000, retries: 0 });
createAppServer(parse).listen(port, '127.0.0.1', () => console.log('Open http://127.0.0.1:' + port));
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try { main(); } catch (error) { console.error(error.message); process.exitCode = 1; }
}
Try the complete example on your computer
After completing the setup above, save the source in your application folder and start the app:
node industry-selector.node.mjsOpen 127.0.0.1:8000 to use the form. The server keeps your secret key out of the page.
For every field and parameter, see Industry API reference.