Have a Book, a Gdynia printer serving European educational publishers
Have a Book has operated from Gdynia since 2007, from an address on Wierzbowa street, with a staff in the tens rather than the hundreds. It describes itself as a single point of service for educational publishers in Europe: pre-press and typesetting, graphic design, printing and binding, and conversion of printed titles into electronic formats with attention to WCAG accessibility. Its clients are educational publishers in Norway, France, Switzerland and Belgium.
That profile decides the shape of the site. An educational publisher does not buy on impulse and does not buy a single book. It buys a series of titles in several variants, and the decision is taken by a group: somebody is looking at the budget, somebody else at the delivery date, and somebody else again at whether this printer can handle a particular binding on a textbook that will be opened every day for three years. A site that serves that decision needs three things at once: a price, a date, and evidence of finish quality.
The Scandinavian market then adds requirements of its own. The site carries content in Swedish, Norwegian and Danish as well as English, and that means considerably more than four sets of translated strings.
Four languages is not four sets of translations
The most common mistake in multilingual projects is treating language as a label placed over the same data. On a Nordic market that shortcut breaks in a place nobody checks before handover: the sorting of lists.
The Swedish and the Danish-Norwegian alphabets contain letters English does not have and, more importantly, place them in a different order from one another. This is not typographic decoration, it is a collation rule. A list of titles or of clients sorted by one rule for every language is simply unsorted for part of the audience, and catalogue search adds a second failure on top: a phrase typed without the diacritic will not find the entry that carries it. The fix is to make the text comparison rule a property of the language version rather than a global database setting.
The second issue is the difference between a language and a market. A client in Norway and a client in Sweden may both read the site in English and still need different paper formats and different vocabulary for binding styles. The content model therefore has to allow language and market to be two axes rather than one. Sites that fuse them end up translating the same data a second time purely to swap one table.
The third concerns addresses. Every language version needs its own stable address and a declared link to the others, or a search engine will show the Swedish publisher the English version, which in practice means an enquiry that never arrives.
The quote is a function, not a price
The deepest difference between this project and an ordinary shop sits in one place: the product here has no price. A book to be printed is a set of parameters, and the price is the result of an operation on that set. Format, paper weight, page count, binding, colour or mono, run length, finishing. Each parameter changes the result and several of them change it in steps.
A price list cannot absorb that. The number of combinations grows multiplicatively, so writing them out as catalogue entries stops being feasible after the third parameter. The quote has to be computed on request from a rate structure rather than read off a list, and that in turn sets the rest of the architecture, because the inputs arrive from a form and the output has to appear immediately and be reproducible.
The second property of this domain is less obvious and easy to miss in code. Print cost does not rise linearly with run length, because setting up the press costs the same for a hundred copies as for ten thousand and is spread across the run. On top of that, a change of format can move the job to a different press or a different sheet, which shifts the cost in a step rather than along a curve. A naive implementation interpolating between two points of the rate table produces results that are correct in the middle of a range and wrong exactly at the thresholds, which is where clients ask most often. The calculation has to reproduce the steps, not a curve drawn through them.
Catalogue and portfolio
The catalogue of printing and publishing services is a view filtered by category, service type and specification. Filtering happens inside the query rather than in the browser, for the same reason it does in any technical catalogue: the set will grow, and shipping all of it to the browser stops paying off earlier than anybody notices while testing on sample data.
There is a further property that separates this from a product catalogue. An entry in a printer’s offer is not a thing but a production capability, and capabilities are described in ranges: from this many pages to that many, in these formats, on paper of this weight, with these bindings. Carried into the content model that means an attribute is often an interval rather than a value, and a specification filter has to ask about containment in an interval rather than equality. Filters built on equality look correct during the build and quietly hide half the offer the moment somebody types a page count that is not one of the listed variants.
The portfolio serves a purpose here that it does not serve on most service sites. A publisher judges a printer by how the finished book looks, and a low-resolution cover shot says nothing about how the spine behaves after a year of use or how a colour falls on a given paper. Galleries therefore have to show detail, which means large files, and that is where the separate work on not loading everything at once comes from. Previews go first, full versions only on opening, and the files themselves are served from the delivery layer rather than from the application server.
Challenges and implemented programming solutions
The first challenge was the quote engine exposed through its own API endpoints. The form sends a set of parameters, the server returns a result. The decisive question is where the calculation lives. Computing the price in the browser is tempting, because the result appears instantly and costs no request. Tempting and wrong: the rate structure becomes public, and the copy of the logic in the browser drifts from the server at the first rate change nobody carries across both ways. The calculation therefore sits on the server, and the browser is responsible only for gathering parameters and displaying the result.
The second challenge was making a quote reproducible. A price given on Monday has to be recoverable on Thursday, even if the rate table changed in between. So what is stored alongside a quote is the full parameter set and the version of the rate structure it came from, not just the number. Without that, every rate change invalidates history and there is no way to answer the simple question of why the client saw that figure. The same decision settles caching: the key for a stored result has to include the rate version, or the site will serve stale figures for a while after an update and leave no trace of it in the logs.
The third challenge was performance with a lot of media. An in-memory cache shortens the database work for everything that has to pass through the application layer anyway: catalogue sets, rate tables, settings. A delivery network takes over the files. Two different problems, and worth separating explicitly because they are often confused: an in-memory cache will not speed up fetching a full-resolution photograph, and an edge layer will not shorten a database query.
The fourth challenge was accepting files. A print order is not a shopping basket, it is a production job with material attached. A print-ready file is often large, and pushing it through an ordinary form fails on server-side limits and on dropped connections. Beyond that, a file that looks fine in a browser can be useless in production because the colour space is wrong or the bleed is missing. A check on receipt is part of the process rather than an addition to it, because catching that error at intake costs a minute and catching it at the press costs the run.
The fifth challenge was synchronising with the company’s own systems: production schedules and material availability. The application does not query the production system while rendering a page. Data is pulled independently and cached with a lifetime chosen per source, because a schedule changes on a different rhythm from a rate table. The important part is the failure behaviour: when a source is unavailable the view shows the last known value or a variant without that section, never an error message. A visitor has no reason to learn that one internal system is not answering right now.
Tools and technologies
The back end runs on PHP and MySQL, the interactive layer on JavaScript and purpose-built API endpoints, and the fast read paths on an in-memory cache. Infrastructure is cloud-hosted and the code is kept under version control, which carries a specific meaning on this project: the rate table is data and the rules for applying it are code, and only keeping those two apart allows a price change without a deployment and a rule change without touching prices.
The presentation layer rests on responsive standards and a stylesheet preprocessor that keeps breakpoints in one place. On a site with a form of a dozen or more fields that is not cosmetic. The quote form is the most important view in the project and simultaneously the hardest to lay out on a narrow screen, because the fields depend on each other and changing one is often visible in another.
Accessibility is part of what the client sells
One thing worth recording that usually goes unsaid in a project write-up: Have a Book converts publications into electronic formats with attention to WCAG accessibility, which means it sells a competence the site itself has to demonstrate. The consequence is simple and uncomfortable. The site of a company selling accessible publications cannot have a catalogue that fails on keyboard operation or specification tables whose headers are not associated with their cells.
A technical parameter table without that association is, to a screen reader, a sequence of numbers with no meaning, and on this site specification tables are the main content. The quote form carries the same obligation: fields that depend on each other logically have to be linked in the markup too, or the news that choosing a binding narrowed the available formats reaches only the person looking at the right part of the screen.
Support and development of the site
Maintenance covers updates, monitoring and ongoing changes. A site with a calculator adds an obligation an ordinary brochure does not have: changes to the rate structure have to be checked against historical data. A change that looks correct on one example can move the result at a different run-length threshold, and the first to notice will be a client holding a figure that disagrees with last week’s.
Updates go to a copy of production first, and that is not a formality. An empty installation has no catalogue of this size, no four language versions of the same entry, no gallery with production-resolution files, and no stored quotes from several rate versions. All four are what updates break on, and none of them exist in a test environment built from scratch.
Backups follow the rule that applies everywhere: one nobody has ever restored is not a backup, it is a file. This project adds a second point, since files uploaded by clients are production material rather than site content, and losing them is an event of a different class from losing a service description.
How the project ran
The build took around six weeks from agreeing the scope to publication. The layout and the placement of elements came from the client, so the time went into the parts a mockup does not show: a content model separating language from market, the rate structure and the rules applying it, the quote endpoints, and the intake and checking of files.
Testing ran against a copy of production. With a calculator that carries particular weight, because the correctness of a quote is not a property you can see on screen. It has to be checked by comparing results against what the company would work out by hand, and it has to be checked at the thresholds rather than in the middle of the ranges. One test case per parameter gives an impression of coverage and says nothing about behaviour at the crossing between presses or sheet formats.
After launch the site moved into maintenance: updates, monitoring, rate structure changes and catalogue growth as the offer changed. It is worth flagging that the implementation dates from 2015 and that some of the work, such as handling large uploads directly, was a task to be built then and is largely handled by off-the-shelf services now.
Summary and requirements analysis
The haveabook.pl site had to answer four requirements at once. First, presenting an offer that cannot be described by a price list, because the price is the result of an operation on parameters. Second, carrying four language versions, three of them Nordic, with the text ordering rules that belong to each. Third, connecting to the company’s internal systems in a way that does not pass their outages on to the site. Fourth, accepting production material and not only orders.
The whole construction follows from those four: calculation on the server, versioning the rate structure alongside every stored quote, a cache key that includes that version, language and market as separate axes in the content model, and a file delivery layer kept away from the application server. The result supports the daily work of a publisher and printer rather than only describing what they do.
Frequently asked questions
Practical answers to apply the topic in real execution.
What scope did the haveabook.pl project cover?
#How did delivery run for haveabook.pl?
#What was the hardest technical part of haveabook.pl?
#What part of haveabook.pl could be reused on another build?
#Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.
Let’s discuss