Skip to content

Natural Language Processing Basics for Web Developers

A practical introduction to the NLP concepts web developers need: tokenization, sentiment analysis, named entity recognition and integrating NLP APIs.

4 min read
Flowchart showing text processing pipeline from raw input to structured data

Natural language processing is no longer confined to research labs. If you build applications that handle text — search, reviews, support tickets, content moderation — you are dealing with NLP problems. The bar for integrating NLP into production web apps has dropped dramatically with pre-trained models and cloud APIs.

This is not a machine learning theory guide. It is a practical walkthrough of the NLP concepts and tools web developers encounter most often, with working examples you can adapt.

Tokenization: Breaking Text into Pieces

Every NLP task starts with tokenization — splitting raw text into meaningful units. How you tokenize affects everything downstream.

pypython
# ❌ Naive split breaks on common edge cases
text = "I can't believe it's $9.99! Ship to New York."
tokens = text.split(" ")
# ['I', "can't", 'believe', "it's", '$9.99!', 'Ship', 'to', 'New', 'York.']
# "can't", "$9.99!", "York." — punctuation attached to words
pypython
# ✅ Proper tokenization with spaCy
import spacy
 
nlp = spacy.load("en_core_web_sm")
doc = nlp("I can't believe it's $9.99! Ship to New York.")
 
tokens = [token.text for token in doc]
# ['I', 'ca', "n't", 'believe', 'it', "'s", '$', '9.99', '!',
#  'Ship', 'to', 'New', 'York', '.']
# Contractions split correctly, punctuation separated, prices preserved

spaCy's tokenizer handles contractions, punctuation, URLs, and currency. For subword tokenization used by transformer models, libraries like Hugging Face's tokenizers split words further into fragments the model understands.

Sentiment Analysis

Sentiment analysis classifies text as positive, negative, or neutral. Common uses: product review aggregation, support ticket prioritization, and social media monitoring.

pypython
from transformers import pipeline
 
# Pre-trained sentiment model — downloads on first run (~250MB)
sentiment = pipeline("sentiment-analysis")
 
reviews = [
    "The battery life is incredible, lasts two full days.",
    "Terrible customer support. Waited 3 hours on hold.",
    "It works fine. Nothing special but does the job.",
]
 
for review in reviews:
    result = sentiment(review)[0]
    print(f"{result['label']:>8} ({result['score']:.3f}): {review[:50]}")
 
# Output:
# POSITIVE (0.999): The battery life is incredible, lasts two full da
# NEGATIVE (0.999): Terrible customer support. Waited 3 hours on hold
# POSITIVE (0.876): It works fine. Nothing special but does the job.

The third review gets classified as POSITIVE with lower confidence — a reasonable result since "works fine" is mildly positive. For production use, consider treating scores below 0.8 as "neutral" or "uncertain" rather than trusting the binary label.

Named Entity Recognition

Named Entity Recognition (NER) extracts structured data from unstructured text — person names, organizations, locations, dates, monetary amounts, and more.

pypython
import spacy
 
nlp = spacy.load("en_core_web_sm")
 
text = """
Apple announced a $3 billion investment in its Austin, Texas facility
on March 15, 2021. CEO Tim Cook said the expansion would create
5,000 new jobs by 2024.
"""
 
doc = nlp(text)
 
for ent in doc.ents:
    print(f"{ent.label_:>10}: {ent.text}")
 
# Output:
#        ORG: Apple
#      MONEY: $3 billion
#        GPE: Austin
#        GPE: Texas
#       DATE: March 15, 2021
#     PERSON: Tim Cook
#   CARDINAL: 5,000
#       DATE: 2024

NER turns free text into structured data you can index, filter, and aggregate. A support ticket mentioning "Microsoft Azure" and "$500 overcharge" can be automatically tagged with the vendor and the financial impact.

tstypescript
// Using NER results in a web application
interface ExtractedEntity {
  text: string;
  label: string;
  start: number;
  end: number;
}
 
interface TicketAnalysis {
  entities: ExtractedEntity[];
  vendor: string | null;
  amount: number | null;
  priority: 'low' | 'medium' | 'high';
}
 
function analyzeTicket(nerResults: ExtractedEntity[]): TicketAnalysis {
  const vendor = nerResults.find(e => e.label === 'ORG')?.text ?? null;
  const moneyEntity = nerResults.find(e => e.label === 'MONEY');
 
  const amount = moneyEntity
    ? parseFloat(moneyEntity.text.replace(/[^0-9.]/g, ''))
    : null;
 
  const priority = amount && amount > 1000 ? 'high'
    : amount && amount > 100 ? 'medium'
    : 'low';
 
  return { entities: nerResults, vendor, amount, priority };
}

Vector embeddings convert text into numerical representations. Similar texts produce similar vectors. This powers semantic search — finding results based on meaning, not just keyword matching.

pypython
from sentence_transformers import SentenceTransformer
import numpy as np
 
model = SentenceTransformer('all-MiniLM-L6-v2')
 
# Knowledge base articles
articles = [
    "How to reset your password and recover account access",
    "Setting up two-factor authentication for your account",
    "Understanding your monthly billing statement",
    "Upgrading your subscription from free to premium",
    "Troubleshooting login errors and account lockouts",
]
 
# Compute embeddings (do this once, store in database)
article_embeddings = model.encode(articles)
 
# User query
query = "I forgot my login credentials"
query_embedding = model.encode([query])
 
# Cosine similarity
similarities = np.dot(article_embeddings, query_embedding.T).flatten()
 
# Rank by relevance
ranked = sorted(enumerate(similarities), key=lambda x: x[1], reverse=True)
 
for idx, score in ranked[:3]:
    print(f"{score:.3f}: {articles[idx]}")
 
# Output:
# 0.687: How to reset your password and recover account access
# 0.542: Troubleshooting login errors and account lockouts
# 0.213: Setting up two-factor authentication for your account

The query "I forgot my login credentials" matches the password reset article even though the words are completely different. The vector embedding captures the semantic meaning, not just keyword overlap.

Integrating NLP via Cloud APIs

For production apps where you do not want to manage model infrastructure, cloud NLP APIs provide the same capabilities as HTTP endpoints.

tstypescript
// ❌ Sending unbounded user text without validation
async function analyzeText(userInput: string) {
  const response = await fetch('https://api.example.com/nlp/analyze', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${process.env.NLP_API_KEY}` },
    body: JSON.stringify({ text: userInput }),
  });
  return response.json();
}
tstypescript
// ✅ Validate and truncate input before sending to the API
const MAX_TEXT_LENGTH = 5000;
 
async function analyzeText(userInput: string): Promise<NLPResult> {
  const sanitized = userInput.trim();
 
  if (sanitized.length === 0) {
    return { entities: [], sentiment: null, error: 'Empty input' };
  }
 
  // Truncate to API limit — most NLP APIs have token limits
  const text = sanitized.slice(0, MAX_TEXT_LENGTH);
 
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 10_000);
 
  try {
    const response = await fetch('https://api.example.com/nlp/analyze', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.NLP_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ text }),
      signal: controller.signal,
    });
 
    if (!response.ok) {
      throw new Error(`NLP API error: ${response.status}`);
    }
 
    return await response.json();
  } finally {
    clearTimeout(timeout);
  }
}

Key production considerations: always set request timeouts, truncate input to API limits, validate the response shape, and cache results for identical inputs.

Preprocessing Text for Better Results

Raw user text is messy. Preprocessing improves NLP accuracy by normalizing the input before analysis.

pypython
import re
import unicodedata
 
def preprocess_text(text: str) -> str:
    # Normalize unicode (é → e, ñ → n for matching purposes)
    text = unicodedata.normalize('NFKD', text)
 
    # Remove URLs
    text = re.sub(r'https?://\S+', '', text)
 
    # Remove excessive whitespace
    text = re.sub(r'\s+', ' ', text).strip()
 
    # Lowercase for comparison (keep original for display)
    return text.lower()
 
# Example:
raw = "Check out  https://example.com — it's AMAZING!!!   🚀🚀🚀"
clean = preprocess_text(raw)
# "check out — it's amazing!!! 🚀🚀🚀"

Do not over-preprocess. Removing stop words ("the", "is", "at") was important for older statistical models but hurts modern transformer models that understand grammar. Match your preprocessing to the model you are using.

Key Takeaways

  1. Tokenization is the foundation — use a proper tokenizer like spaCy instead of naive string splitting
  2. Sentiment analysis needs confidence thresholds — treat low-confidence predictions as neutral rather than guessing
  3. NER extracts structure from chaos — automatically tag text with people, organizations, amounts, and dates
  4. Semantic search beats keyword search — vector embeddings find results by meaning, not string matching
  5. Validate before calling NLP APIs — truncate input, set timeouts, cache results for identical queries
  6. Match preprocessing to your model — modern transformers need less preprocessing than older statistical approaches
Wilfredo Rujel

Wilfredo Rujel

Full Stack Software Engineer

Share this postX